sqlglot.generator
1from __future__ import annotations 2 3import logging 4import re 5import typing as t 6from collections import defaultdict 7from functools import reduce 8 9from sqlglot import exp 10from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages 11from sqlglot.helper import apply_index_offset, csv, name_sequence, seq_get 12from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS 13from sqlglot.time import format_time 14from sqlglot.tokens import TokenType 15 16if t.TYPE_CHECKING: 17 from sqlglot._typing import E 18 from sqlglot.dialects.dialect import DialectType 19 20logger = logging.getLogger("sqlglot") 21 22ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)") 23 24 25class _Generator(type): 26 def __new__(cls, clsname, bases, attrs): 27 klass = super().__new__(cls, clsname, bases, attrs) 28 29 # Remove transforms that correspond to unsupported JSONPathPart expressions 30 for part in ALL_JSON_PATH_PARTS - klass.SUPPORTED_JSON_PATH_PARTS: 31 klass.TRANSFORMS.pop(part, None) 32 33 return klass 34 35 36class Generator(metaclass=_Generator): 37 """ 38 Generator converts a given syntax tree to the corresponding SQL string. 39 40 Args: 41 pretty: Whether to format the produced SQL string. 42 Default: False. 43 identify: Determines when an identifier should be quoted. Possible values are: 44 False (default): Never quote, except in cases where it's mandatory by the dialect. 45 True or 'always': Always quote. 46 'safe': Only quote identifiers that are case insensitive. 47 normalize: Whether to normalize identifiers to lowercase. 48 Default: False. 49 pad: The pad size in a formatted string. For example, this affects the indentation of 50 a projection in a query, relative to its nesting level. 51 Default: 2. 52 indent: The indentation size in a formatted string. For example, this affects the 53 indentation of subqueries and filters under a `WHERE` clause. 54 Default: 2. 55 normalize_functions: How to normalize function names. Possible values are: 56 "upper" or True (default): Convert names to uppercase. 57 "lower": Convert names to lowercase. 58 False: Disables function name normalization. 59 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 60 Default ErrorLevel.WARN. 61 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 62 This is only relevant if unsupported_level is ErrorLevel.RAISE. 63 Default: 3 64 leading_comma: Whether the comma is leading or trailing in select expressions. 65 This is only relevant when generating in pretty mode. 66 Default: False 67 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 68 The default is on the smaller end because the length only represents a segment and not the true 69 line length. 70 Default: 80 71 comments: Whether to preserve comments in the output SQL code. 72 Default: True 73 """ 74 75 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 76 **JSON_PATH_PART_TRANSFORMS, 77 exp.AllowedValuesProperty: lambda self, 78 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 79 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 80 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 81 exp.CaseSpecificColumnConstraint: lambda _, 82 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 83 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 84 exp.CharacterSetProperty: lambda self, 85 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 86 exp.ClusteredColumnConstraint: lambda self, 87 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 88 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 89 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 90 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 91 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 92 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 93 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 94 exp.DynamicProperty: lambda *_: "DYNAMIC", 95 exp.EmptyProperty: lambda *_: "EMPTY", 96 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 97 exp.EphemeralColumnConstraint: lambda self, 98 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 99 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 100 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 101 exp.Except: lambda self, e: self.set_operations(e), 102 exp.ExternalProperty: lambda *_: "EXTERNAL", 103 exp.GlobalProperty: lambda *_: "GLOBAL", 104 exp.HeapProperty: lambda *_: "HEAP", 105 exp.IcebergProperty: lambda *_: "ICEBERG", 106 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 107 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 108 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 109 exp.Intersect: lambda self, e: self.set_operations(e), 110 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 111 exp.LanguageProperty: lambda self, e: self.naked_property(e), 112 exp.LocationProperty: lambda self, e: self.naked_property(e), 113 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 114 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 115 exp.NonClusteredColumnConstraint: lambda self, 116 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 117 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 118 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 119 exp.OnCommitProperty: lambda _, 120 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 121 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 122 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 123 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 124 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 125 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 126 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 127 exp.ProjectionPolicyColumnConstraint: lambda self, 128 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 129 exp.RemoteWithConnectionModelProperty: lambda self, 130 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 131 exp.ReturnsProperty: lambda self, e: ( 132 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 133 ), 134 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 135 exp.SecureProperty: lambda *_: "SECURE", 136 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 137 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 138 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 139 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 140 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 141 exp.SqlReadWriteProperty: lambda _, e: e.name, 142 exp.SqlSecurityProperty: lambda _, 143 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 144 exp.StabilityProperty: lambda _, e: e.name, 145 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 146 exp.StreamingTableProperty: lambda *_: "STREAMING", 147 exp.StrictProperty: lambda *_: "STRICT", 148 exp.TemporaryProperty: lambda *_: "TEMPORARY", 149 exp.TagColumnConstraint: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 150 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 151 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 152 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 153 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 154 exp.TransientProperty: lambda *_: "TRANSIENT", 155 exp.Union: lambda self, e: self.set_operations(e), 156 exp.UnloggedProperty: lambda *_: "UNLOGGED", 157 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 158 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 159 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 160 exp.VolatileProperty: lambda *_: "VOLATILE", 161 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 162 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 163 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 164 } 165 166 # Whether null ordering is supported in order by 167 # True: Full Support, None: No support, False: No support in window specifications 168 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 169 170 # Whether ignore nulls is inside the agg or outside. 171 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 172 IGNORE_NULLS_IN_FUNC = False 173 174 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 175 LOCKING_READS_SUPPORTED = False 176 177 # Whether the EXCEPT and INTERSECT operations can return duplicates 178 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 179 180 # Wrap derived values in parens, usually standard but spark doesn't support it 181 WRAP_DERIVED_VALUES = True 182 183 # Whether create function uses an AS before the RETURN 184 CREATE_FUNCTION_RETURN_AS = True 185 186 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 187 MATCHED_BY_SOURCE = True 188 189 # Whether the INTERVAL expression works only with values like '1 day' 190 SINGLE_STRING_INTERVAL = False 191 192 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 193 INTERVAL_ALLOWS_PLURAL_FORM = True 194 195 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 196 LIMIT_FETCH = "ALL" 197 198 # Whether limit and fetch allows expresions or just limits 199 LIMIT_ONLY_LITERALS = False 200 201 # Whether a table is allowed to be renamed with a db 202 RENAME_TABLE_WITH_DB = True 203 204 # The separator for grouping sets and rollups 205 GROUPINGS_SEP = "," 206 207 # The string used for creating an index on a table 208 INDEX_ON = "ON" 209 210 # Whether join hints should be generated 211 JOIN_HINTS = True 212 213 # Whether table hints should be generated 214 TABLE_HINTS = True 215 216 # Whether query hints should be generated 217 QUERY_HINTS = True 218 219 # What kind of separator to use for query hints 220 QUERY_HINT_SEP = ", " 221 222 # Whether comparing against booleans (e.g. x IS TRUE) is supported 223 IS_BOOL_ALLOWED = True 224 225 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 226 DUPLICATE_KEY_UPDATE_WITH_SET = True 227 228 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 229 LIMIT_IS_TOP = False 230 231 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 232 RETURNING_END = True 233 234 # Whether to generate an unquoted value for EXTRACT's date part argument 235 EXTRACT_ALLOWS_QUOTES = True 236 237 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 238 TZ_TO_WITH_TIME_ZONE = False 239 240 # Whether the NVL2 function is supported 241 NVL2_SUPPORTED = True 242 243 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 244 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 245 246 # Whether VALUES statements can be used as derived tables. 247 # MySQL 5 and Redshift do not allow this, so when False, it will convert 248 # SELECT * VALUES into SELECT UNION 249 VALUES_AS_TABLE = True 250 251 # Whether the word COLUMN is included when adding a column with ALTER TABLE 252 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 253 254 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 255 UNNEST_WITH_ORDINALITY = True 256 257 # Whether FILTER (WHERE cond) can be used for conditional aggregation 258 AGGREGATE_FILTER_SUPPORTED = True 259 260 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 261 SEMI_ANTI_JOIN_WITH_SIDE = True 262 263 # Whether to include the type of a computed column in the CREATE DDL 264 COMPUTED_COLUMN_WITH_TYPE = True 265 266 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 267 SUPPORTS_TABLE_COPY = True 268 269 # Whether parentheses are required around the table sample's expression 270 TABLESAMPLE_REQUIRES_PARENS = True 271 272 # Whether a table sample clause's size needs to be followed by the ROWS keyword 273 TABLESAMPLE_SIZE_IS_ROWS = True 274 275 # The keyword(s) to use when generating a sample clause 276 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 277 278 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 279 TABLESAMPLE_WITH_METHOD = True 280 281 # The keyword to use when specifying the seed of a sample clause 282 TABLESAMPLE_SEED_KEYWORD = "SEED" 283 284 # Whether COLLATE is a function instead of a binary operator 285 COLLATE_IS_FUNC = False 286 287 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 288 DATA_TYPE_SPECIFIERS_ALLOWED = False 289 290 # Whether conditions require booleans WHERE x = 0 vs WHERE x 291 ENSURE_BOOLS = False 292 293 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 294 CTE_RECURSIVE_KEYWORD_REQUIRED = True 295 296 # Whether CONCAT requires >1 arguments 297 SUPPORTS_SINGLE_ARG_CONCAT = True 298 299 # Whether LAST_DAY function supports a date part argument 300 LAST_DAY_SUPPORTS_DATE_PART = True 301 302 # Whether named columns are allowed in table aliases 303 SUPPORTS_TABLE_ALIAS_COLUMNS = True 304 305 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 306 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 307 308 # What delimiter to use for separating JSON key/value pairs 309 JSON_KEY_VALUE_PAIR_SEP = ":" 310 311 # INSERT OVERWRITE TABLE x override 312 INSERT_OVERWRITE = " OVERWRITE TABLE" 313 314 # Whether the SELECT .. INTO syntax is used instead of CTAS 315 SUPPORTS_SELECT_INTO = False 316 317 # Whether UNLOGGED tables can be created 318 SUPPORTS_UNLOGGED_TABLES = False 319 320 # Whether the CREATE TABLE LIKE statement is supported 321 SUPPORTS_CREATE_TABLE_LIKE = True 322 323 # Whether the LikeProperty needs to be specified inside of the schema clause 324 LIKE_PROPERTY_INSIDE_SCHEMA = False 325 326 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 327 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 328 MULTI_ARG_DISTINCT = True 329 330 # Whether the JSON extraction operators expect a value of type JSON 331 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 332 333 # Whether bracketed keys like ["foo"] are supported in JSON paths 334 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 335 336 # Whether to escape keys using single quotes in JSON paths 337 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 338 339 # The JSONPathPart expressions supported by this dialect 340 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 341 342 # Whether any(f(x) for x in array) can be implemented by this dialect 343 CAN_IMPLEMENT_ARRAY_ANY = False 344 345 # Whether the function TO_NUMBER is supported 346 SUPPORTS_TO_NUMBER = True 347 348 # Whether or not set op modifiers apply to the outer set op or select. 349 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 350 # True means limit 1 happens after the set op, False means it it happens on y. 351 SET_OP_MODIFIERS = True 352 353 # Whether parameters from COPY statement are wrapped in parentheses 354 COPY_PARAMS_ARE_WRAPPED = True 355 356 # Whether values of params are set with "=" token or empty space 357 COPY_PARAMS_EQ_REQUIRED = False 358 359 # Whether COPY statement has INTO keyword 360 COPY_HAS_INTO_KEYWORD = True 361 362 # Whether the conditional TRY(expression) function is supported 363 TRY_SUPPORTED = True 364 365 # Whether the UESCAPE syntax in unicode strings is supported 366 SUPPORTS_UESCAPE = True 367 368 # The keyword to use when generating a star projection with excluded columns 369 STAR_EXCEPT = "EXCEPT" 370 371 # The HEX function name 372 HEX_FUNC = "HEX" 373 374 # The keywords to use when prefixing & separating WITH based properties 375 WITH_PROPERTIES_PREFIX = "WITH" 376 377 # Whether to quote the generated expression of exp.JsonPath 378 QUOTE_JSON_PATH = True 379 380 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 381 PAD_FILL_PATTERN_IS_REQUIRED = False 382 383 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 384 SUPPORTS_EXPLODING_PROJECTIONS = True 385 386 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 387 ARRAY_CONCAT_IS_VAR_LEN = True 388 389 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 390 SUPPORTS_CONVERT_TIMEZONE = False 391 392 # Whether nullable types can be constructed, e.g. `Nullable(Int64)` 393 SUPPORTS_NULLABLE_TYPES = True 394 395 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 396 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 397 398 TYPE_MAPPING = { 399 exp.DataType.Type.NCHAR: "CHAR", 400 exp.DataType.Type.NVARCHAR: "VARCHAR", 401 exp.DataType.Type.MEDIUMTEXT: "TEXT", 402 exp.DataType.Type.LONGTEXT: "TEXT", 403 exp.DataType.Type.TINYTEXT: "TEXT", 404 exp.DataType.Type.MEDIUMBLOB: "BLOB", 405 exp.DataType.Type.LONGBLOB: "BLOB", 406 exp.DataType.Type.TINYBLOB: "BLOB", 407 exp.DataType.Type.INET: "INET", 408 exp.DataType.Type.ROWVERSION: "VARBINARY", 409 } 410 411 TIME_PART_SINGULARS = { 412 "MICROSECONDS": "MICROSECOND", 413 "SECONDS": "SECOND", 414 "MINUTES": "MINUTE", 415 "HOURS": "HOUR", 416 "DAYS": "DAY", 417 "WEEKS": "WEEK", 418 "MONTHS": "MONTH", 419 "QUARTERS": "QUARTER", 420 "YEARS": "YEAR", 421 } 422 423 AFTER_HAVING_MODIFIER_TRANSFORMS = { 424 "cluster": lambda self, e: self.sql(e, "cluster"), 425 "distribute": lambda self, e: self.sql(e, "distribute"), 426 "sort": lambda self, e: self.sql(e, "sort"), 427 "windows": lambda self, e: ( 428 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 429 if e.args.get("windows") 430 else "" 431 ), 432 "qualify": lambda self, e: self.sql(e, "qualify"), 433 } 434 435 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 436 437 STRUCT_DELIMITER = ("<", ">") 438 439 PARAMETER_TOKEN = "@" 440 NAMED_PLACEHOLDER_TOKEN = ":" 441 442 PROPERTIES_LOCATION = { 443 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 444 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 445 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 446 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 447 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 448 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 449 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 450 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 451 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 452 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 453 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 454 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 455 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 456 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 457 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 458 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 459 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 460 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 461 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 462 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 463 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 464 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 465 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 466 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 467 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 468 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 469 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 470 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 471 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 472 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 473 exp.HeapProperty: exp.Properties.Location.POST_WITH, 474 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 475 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 476 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 477 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 478 exp.JournalProperty: exp.Properties.Location.POST_NAME, 479 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 480 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 481 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 482 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 483 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 484 exp.LogProperty: exp.Properties.Location.POST_NAME, 485 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 486 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 487 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 488 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 489 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 490 exp.Order: exp.Properties.Location.POST_SCHEMA, 491 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 492 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 493 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 494 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 495 exp.Property: exp.Properties.Location.POST_WITH, 496 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 497 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 498 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 499 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 500 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 501 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 502 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 503 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 504 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 505 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 506 exp.Set: exp.Properties.Location.POST_SCHEMA, 507 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 508 exp.SetProperty: exp.Properties.Location.POST_CREATE, 509 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 510 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 511 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 512 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 513 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 514 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 515 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 516 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 517 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 518 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 519 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 520 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 521 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 522 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 523 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 524 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 525 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 526 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 527 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 528 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 529 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 530 } 531 532 # Keywords that can't be used as unquoted identifier names 533 RESERVED_KEYWORDS: t.Set[str] = set() 534 535 # Expressions whose comments are separated from them for better formatting 536 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 537 exp.Command, 538 exp.Create, 539 exp.Delete, 540 exp.Drop, 541 exp.From, 542 exp.Insert, 543 exp.Join, 544 exp.MultitableInserts, 545 exp.Select, 546 exp.SetOperation, 547 exp.Update, 548 exp.Where, 549 exp.With, 550 ) 551 552 # Expressions that should not have their comments generated in maybe_comment 553 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 554 exp.Binary, 555 exp.SetOperation, 556 ) 557 558 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 559 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 560 exp.Column, 561 exp.Literal, 562 exp.Neg, 563 exp.Paren, 564 ) 565 566 PARAMETERIZABLE_TEXT_TYPES = { 567 exp.DataType.Type.NVARCHAR, 568 exp.DataType.Type.VARCHAR, 569 exp.DataType.Type.CHAR, 570 exp.DataType.Type.NCHAR, 571 } 572 573 # Expressions that need to have all CTEs under them bubbled up to them 574 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 575 576 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 577 578 __slots__ = ( 579 "pretty", 580 "identify", 581 "normalize", 582 "pad", 583 "_indent", 584 "normalize_functions", 585 "unsupported_level", 586 "max_unsupported", 587 "leading_comma", 588 "max_text_width", 589 "comments", 590 "dialect", 591 "unsupported_messages", 592 "_escaped_quote_end", 593 "_escaped_identifier_end", 594 "_next_name", 595 ) 596 597 def __init__( 598 self, 599 pretty: t.Optional[bool] = None, 600 identify: str | bool = False, 601 normalize: bool = False, 602 pad: int = 2, 603 indent: int = 2, 604 normalize_functions: t.Optional[str | bool] = None, 605 unsupported_level: ErrorLevel = ErrorLevel.WARN, 606 max_unsupported: int = 3, 607 leading_comma: bool = False, 608 max_text_width: int = 80, 609 comments: bool = True, 610 dialect: DialectType = None, 611 ): 612 import sqlglot 613 from sqlglot.dialects import Dialect 614 615 self.pretty = pretty if pretty is not None else sqlglot.pretty 616 self.identify = identify 617 self.normalize = normalize 618 self.pad = pad 619 self._indent = indent 620 self.unsupported_level = unsupported_level 621 self.max_unsupported = max_unsupported 622 self.leading_comma = leading_comma 623 self.max_text_width = max_text_width 624 self.comments = comments 625 self.dialect = Dialect.get_or_raise(dialect) 626 627 # This is both a Dialect property and a Generator argument, so we prioritize the latter 628 self.normalize_functions = ( 629 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 630 ) 631 632 self.unsupported_messages: t.List[str] = [] 633 self._escaped_quote_end: str = ( 634 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 635 ) 636 self._escaped_identifier_end: str = ( 637 self.dialect.tokenizer_class.IDENTIFIER_ESCAPES[0] + self.dialect.IDENTIFIER_END 638 ) 639 640 self._next_name = name_sequence("_t") 641 642 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 643 """ 644 Generates the SQL string corresponding to the given syntax tree. 645 646 Args: 647 expression: The syntax tree. 648 copy: Whether to copy the expression. The generator performs mutations so 649 it is safer to copy. 650 651 Returns: 652 The SQL string corresponding to `expression`. 653 """ 654 if copy: 655 expression = expression.copy() 656 657 expression = self.preprocess(expression) 658 659 self.unsupported_messages = [] 660 sql = self.sql(expression).strip() 661 662 if self.pretty: 663 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 664 665 if self.unsupported_level == ErrorLevel.IGNORE: 666 return sql 667 668 if self.unsupported_level == ErrorLevel.WARN: 669 for msg in self.unsupported_messages: 670 logger.warning(msg) 671 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 672 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 673 674 return sql 675 676 def preprocess(self, expression: exp.Expression) -> exp.Expression: 677 """Apply generic preprocessing transformations to a given expression.""" 678 if ( 679 not expression.parent 680 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 681 and any(node.parent is not expression for node in expression.find_all(exp.With)) 682 ): 683 from sqlglot.transforms import move_ctes_to_top_level 684 685 expression = move_ctes_to_top_level(expression) 686 687 if self.ENSURE_BOOLS: 688 from sqlglot.transforms import ensure_bools 689 690 expression = ensure_bools(expression) 691 692 return expression 693 694 def unsupported(self, message: str) -> None: 695 if self.unsupported_level == ErrorLevel.IMMEDIATE: 696 raise UnsupportedError(message) 697 self.unsupported_messages.append(message) 698 699 def sep(self, sep: str = " ") -> str: 700 return f"{sep.strip()}\n" if self.pretty else sep 701 702 def seg(self, sql: str, sep: str = " ") -> str: 703 return f"{self.sep(sep)}{sql}" 704 705 def pad_comment(self, comment: str) -> str: 706 comment = " " + comment if comment[0].strip() else comment 707 comment = comment + " " if comment[-1].strip() else comment 708 return comment 709 710 def maybe_comment( 711 self, 712 sql: str, 713 expression: t.Optional[exp.Expression] = None, 714 comments: t.Optional[t.List[str]] = None, 715 separated: bool = False, 716 ) -> str: 717 comments = ( 718 ((expression and expression.comments) if comments is None else comments) # type: ignore 719 if self.comments 720 else None 721 ) 722 723 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 724 return sql 725 726 comments_sql = " ".join( 727 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 728 ) 729 730 if not comments_sql: 731 return sql 732 733 comments_sql = self._replace_line_breaks(comments_sql) 734 735 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 736 return ( 737 f"{self.sep()}{comments_sql}{sql}" 738 if not sql or sql[0].isspace() 739 else f"{comments_sql}{self.sep()}{sql}" 740 ) 741 742 return f"{sql} {comments_sql}" 743 744 def wrap(self, expression: exp.Expression | str) -> str: 745 this_sql = ( 746 self.sql(expression) 747 if isinstance(expression, exp.UNWRAPPED_QUERIES) 748 else self.sql(expression, "this") 749 ) 750 if not this_sql: 751 return "()" 752 753 this_sql = self.indent(this_sql, level=1, pad=0) 754 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 755 756 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 757 original = self.identify 758 self.identify = False 759 result = func(*args, **kwargs) 760 self.identify = original 761 return result 762 763 def normalize_func(self, name: str) -> str: 764 if self.normalize_functions == "upper" or self.normalize_functions is True: 765 return name.upper() 766 if self.normalize_functions == "lower": 767 return name.lower() 768 return name 769 770 def indent( 771 self, 772 sql: str, 773 level: int = 0, 774 pad: t.Optional[int] = None, 775 skip_first: bool = False, 776 skip_last: bool = False, 777 ) -> str: 778 if not self.pretty or not sql: 779 return sql 780 781 pad = self.pad if pad is None else pad 782 lines = sql.split("\n") 783 784 return "\n".join( 785 ( 786 line 787 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 788 else f"{' ' * (level * self._indent + pad)}{line}" 789 ) 790 for i, line in enumerate(lines) 791 ) 792 793 def sql( 794 self, 795 expression: t.Optional[str | exp.Expression], 796 key: t.Optional[str] = None, 797 comment: bool = True, 798 ) -> str: 799 if not expression: 800 return "" 801 802 if isinstance(expression, str): 803 return expression 804 805 if key: 806 value = expression.args.get(key) 807 if value: 808 return self.sql(value) 809 return "" 810 811 transform = self.TRANSFORMS.get(expression.__class__) 812 813 if callable(transform): 814 sql = transform(self, expression) 815 elif isinstance(expression, exp.Expression): 816 exp_handler_name = f"{expression.key}_sql" 817 818 if hasattr(self, exp_handler_name): 819 sql = getattr(self, exp_handler_name)(expression) 820 elif isinstance(expression, exp.Func): 821 sql = self.function_fallback_sql(expression) 822 elif isinstance(expression, exp.Property): 823 sql = self.property_sql(expression) 824 else: 825 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 826 else: 827 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 828 829 return self.maybe_comment(sql, expression) if self.comments and comment else sql 830 831 def uncache_sql(self, expression: exp.Uncache) -> str: 832 table = self.sql(expression, "this") 833 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 834 return f"UNCACHE TABLE{exists_sql} {table}" 835 836 def cache_sql(self, expression: exp.Cache) -> str: 837 lazy = " LAZY" if expression.args.get("lazy") else "" 838 table = self.sql(expression, "this") 839 options = expression.args.get("options") 840 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 841 sql = self.sql(expression, "expression") 842 sql = f" AS{self.sep()}{sql}" if sql else "" 843 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 844 return self.prepend_ctes(expression, sql) 845 846 def characterset_sql(self, expression: exp.CharacterSet) -> str: 847 if isinstance(expression.parent, exp.Cast): 848 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 849 default = "DEFAULT " if expression.args.get("default") else "" 850 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 851 852 def column_parts(self, expression: exp.Column) -> str: 853 return ".".join( 854 self.sql(part) 855 for part in ( 856 expression.args.get("catalog"), 857 expression.args.get("db"), 858 expression.args.get("table"), 859 expression.args.get("this"), 860 ) 861 if part 862 ) 863 864 def column_sql(self, expression: exp.Column) -> str: 865 join_mark = " (+)" if expression.args.get("join_mark") else "" 866 867 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 868 join_mark = "" 869 self.unsupported("Outer join syntax using the (+) operator is not supported.") 870 871 return f"{self.column_parts(expression)}{join_mark}" 872 873 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 874 this = self.sql(expression, "this") 875 this = f" {this}" if this else "" 876 position = self.sql(expression, "position") 877 return f"{position}{this}" 878 879 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 880 column = self.sql(expression, "this") 881 kind = self.sql(expression, "kind") 882 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 883 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 884 kind = f"{sep}{kind}" if kind else "" 885 constraints = f" {constraints}" if constraints else "" 886 position = self.sql(expression, "position") 887 position = f" {position}" if position else "" 888 889 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 890 kind = "" 891 892 return f"{exists}{column}{kind}{constraints}{position}" 893 894 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 895 this = self.sql(expression, "this") 896 kind_sql = self.sql(expression, "kind").strip() 897 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 898 899 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 900 this = self.sql(expression, "this") 901 if expression.args.get("not_null"): 902 persisted = " PERSISTED NOT NULL" 903 elif expression.args.get("persisted"): 904 persisted = " PERSISTED" 905 else: 906 persisted = "" 907 return f"AS {this}{persisted}" 908 909 def autoincrementcolumnconstraint_sql(self, _) -> str: 910 return self.token_sql(TokenType.AUTO_INCREMENT) 911 912 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 913 if isinstance(expression.this, list): 914 this = self.wrap(self.expressions(expression, key="this", flat=True)) 915 else: 916 this = self.sql(expression, "this") 917 918 return f"COMPRESS {this}" 919 920 def generatedasidentitycolumnconstraint_sql( 921 self, expression: exp.GeneratedAsIdentityColumnConstraint 922 ) -> str: 923 this = "" 924 if expression.this is not None: 925 on_null = " ON NULL" if expression.args.get("on_null") else "" 926 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 927 928 start = expression.args.get("start") 929 start = f"START WITH {start}" if start else "" 930 increment = expression.args.get("increment") 931 increment = f" INCREMENT BY {increment}" if increment else "" 932 minvalue = expression.args.get("minvalue") 933 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 934 maxvalue = expression.args.get("maxvalue") 935 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 936 cycle = expression.args.get("cycle") 937 cycle_sql = "" 938 939 if cycle is not None: 940 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 941 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 942 943 sequence_opts = "" 944 if start or increment or cycle_sql: 945 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 946 sequence_opts = f" ({sequence_opts.strip()})" 947 948 expr = self.sql(expression, "expression") 949 expr = f"({expr})" if expr else "IDENTITY" 950 951 return f"GENERATED{this} AS {expr}{sequence_opts}" 952 953 def generatedasrowcolumnconstraint_sql( 954 self, expression: exp.GeneratedAsRowColumnConstraint 955 ) -> str: 956 start = "START" if expression.args.get("start") else "END" 957 hidden = " HIDDEN" if expression.args.get("hidden") else "" 958 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 959 960 def periodforsystemtimeconstraint_sql( 961 self, expression: exp.PeriodForSystemTimeConstraint 962 ) -> str: 963 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 964 965 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 966 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 967 968 def transformcolumnconstraint_sql(self, expression: exp.TransformColumnConstraint) -> str: 969 return f"AS {self.sql(expression, 'this')}" 970 971 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 972 desc = expression.args.get("desc") 973 if desc is not None: 974 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 975 return "PRIMARY KEY" 976 977 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 978 this = self.sql(expression, "this") 979 this = f" {this}" if this else "" 980 index_type = expression.args.get("index_type") 981 index_type = f" USING {index_type}" if index_type else "" 982 on_conflict = self.sql(expression, "on_conflict") 983 on_conflict = f" {on_conflict}" if on_conflict else "" 984 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 985 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}" 986 987 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 988 return self.sql(expression, "this") 989 990 def create_sql(self, expression: exp.Create) -> str: 991 kind = self.sql(expression, "kind") 992 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 993 properties = expression.args.get("properties") 994 properties_locs = self.locate_properties(properties) if properties else defaultdict() 995 996 this = self.createable_sql(expression, properties_locs) 997 998 properties_sql = "" 999 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1000 exp.Properties.Location.POST_WITH 1001 ): 1002 properties_sql = self.sql( 1003 exp.Properties( 1004 expressions=[ 1005 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1006 *properties_locs[exp.Properties.Location.POST_WITH], 1007 ] 1008 ) 1009 ) 1010 1011 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1012 properties_sql = self.sep() + properties_sql 1013 elif not self.pretty: 1014 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1015 properties_sql = f" {properties_sql}" 1016 1017 begin = " BEGIN" if expression.args.get("begin") else "" 1018 end = " END" if expression.args.get("end") else "" 1019 1020 expression_sql = self.sql(expression, "expression") 1021 if expression_sql: 1022 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1023 1024 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1025 postalias_props_sql = "" 1026 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1027 postalias_props_sql = self.properties( 1028 exp.Properties( 1029 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1030 ), 1031 wrapped=False, 1032 ) 1033 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1034 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1035 1036 postindex_props_sql = "" 1037 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1038 postindex_props_sql = self.properties( 1039 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1040 wrapped=False, 1041 prefix=" ", 1042 ) 1043 1044 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1045 indexes = f" {indexes}" if indexes else "" 1046 index_sql = indexes + postindex_props_sql 1047 1048 replace = " OR REPLACE" if expression.args.get("replace") else "" 1049 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1050 unique = " UNIQUE" if expression.args.get("unique") else "" 1051 1052 clustered = expression.args.get("clustered") 1053 if clustered is None: 1054 clustered_sql = "" 1055 elif clustered: 1056 clustered_sql = " CLUSTERED COLUMNSTORE" 1057 else: 1058 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1059 1060 postcreate_props_sql = "" 1061 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1062 postcreate_props_sql = self.properties( 1063 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1064 sep=" ", 1065 prefix=" ", 1066 wrapped=False, 1067 ) 1068 1069 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1070 1071 postexpression_props_sql = "" 1072 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1073 postexpression_props_sql = self.properties( 1074 exp.Properties( 1075 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1076 ), 1077 sep=" ", 1078 prefix=" ", 1079 wrapped=False, 1080 ) 1081 1082 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1083 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1084 no_schema_binding = ( 1085 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1086 ) 1087 1088 clone = self.sql(expression, "clone") 1089 clone = f" {clone}" if clone else "" 1090 1091 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_sql}{expression_sql}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1092 return self.prepend_ctes(expression, expression_sql) 1093 1094 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1095 start = self.sql(expression, "start") 1096 start = f"START WITH {start}" if start else "" 1097 increment = self.sql(expression, "increment") 1098 increment = f" INCREMENT BY {increment}" if increment else "" 1099 minvalue = self.sql(expression, "minvalue") 1100 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1101 maxvalue = self.sql(expression, "maxvalue") 1102 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1103 owned = self.sql(expression, "owned") 1104 owned = f" OWNED BY {owned}" if owned else "" 1105 1106 cache = expression.args.get("cache") 1107 if cache is None: 1108 cache_str = "" 1109 elif cache is True: 1110 cache_str = " CACHE" 1111 else: 1112 cache_str = f" CACHE {cache}" 1113 1114 options = self.expressions(expression, key="options", flat=True, sep=" ") 1115 options = f" {options}" if options else "" 1116 1117 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1118 1119 def clone_sql(self, expression: exp.Clone) -> str: 1120 this = self.sql(expression, "this") 1121 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1122 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1123 return f"{shallow}{keyword} {this}" 1124 1125 def describe_sql(self, expression: exp.Describe) -> str: 1126 style = expression.args.get("style") 1127 style = f" {style}" if style else "" 1128 partition = self.sql(expression, "partition") 1129 partition = f" {partition}" if partition else "" 1130 return f"DESCRIBE{style} {self.sql(expression, 'this')}{partition}" 1131 1132 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1133 tag = self.sql(expression, "tag") 1134 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1135 1136 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1137 with_ = self.sql(expression, "with") 1138 if with_: 1139 sql = f"{with_}{self.sep()}{sql}" 1140 return sql 1141 1142 def with_sql(self, expression: exp.With) -> str: 1143 sql = self.expressions(expression, flat=True) 1144 recursive = ( 1145 "RECURSIVE " 1146 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1147 else "" 1148 ) 1149 1150 return f"WITH {recursive}{sql}" 1151 1152 def cte_sql(self, expression: exp.CTE) -> str: 1153 alias = self.sql(expression, "alias") 1154 1155 materialized = expression.args.get("materialized") 1156 if materialized is False: 1157 materialized = "NOT MATERIALIZED " 1158 elif materialized: 1159 materialized = "MATERIALIZED " 1160 1161 return f"{alias} AS {materialized or ''}{self.wrap(expression)}" 1162 1163 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1164 alias = self.sql(expression, "this") 1165 columns = self.expressions(expression, key="columns", flat=True) 1166 columns = f"({columns})" if columns else "" 1167 1168 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1169 columns = "" 1170 self.unsupported("Named columns are not supported in table alias.") 1171 1172 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1173 alias = self._next_name() 1174 1175 return f"{alias}{columns}" 1176 1177 def bitstring_sql(self, expression: exp.BitString) -> str: 1178 this = self.sql(expression, "this") 1179 if self.dialect.BIT_START: 1180 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1181 return f"{int(this, 2)}" 1182 1183 def hexstring_sql(self, expression: exp.HexString) -> str: 1184 this = self.sql(expression, "this") 1185 if self.dialect.HEX_START: 1186 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1187 return f"{int(this, 16)}" 1188 1189 def bytestring_sql(self, expression: exp.ByteString) -> str: 1190 this = self.sql(expression, "this") 1191 if self.dialect.BYTE_START: 1192 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1193 return this 1194 1195 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1196 this = self.sql(expression, "this") 1197 escape = expression.args.get("escape") 1198 1199 if self.dialect.UNICODE_START: 1200 escape_substitute = r"\\\1" 1201 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1202 else: 1203 escape_substitute = r"\\u\1" 1204 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1205 1206 if escape: 1207 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1208 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1209 else: 1210 escape_pattern = ESCAPED_UNICODE_RE 1211 escape_sql = "" 1212 1213 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1214 this = escape_pattern.sub(escape_substitute, this) 1215 1216 return f"{left_quote}{this}{right_quote}{escape_sql}" 1217 1218 def rawstring_sql(self, expression: exp.RawString) -> str: 1219 string = self.escape_str(expression.this.replace("\\", "\\\\"), escape_backslash=False) 1220 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1221 1222 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1223 this = self.sql(expression, "this") 1224 specifier = self.sql(expression, "expression") 1225 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1226 return f"{this}{specifier}" 1227 1228 def datatype_sql(self, expression: exp.DataType) -> str: 1229 nested = "" 1230 values = "" 1231 interior = self.expressions(expression, flat=True) 1232 1233 type_value = expression.this 1234 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1235 type_sql = self.sql(expression, "kind") 1236 elif type_value != exp.DataType.Type.NULLABLE or self.SUPPORTS_NULLABLE_TYPES: 1237 type_sql = ( 1238 self.TYPE_MAPPING.get(type_value, type_value.value) 1239 if isinstance(type_value, exp.DataType.Type) 1240 else type_value 1241 ) 1242 else: 1243 return interior 1244 1245 if interior: 1246 if expression.args.get("nested"): 1247 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1248 if expression.args.get("values") is not None: 1249 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1250 values = self.expressions(expression, key="values", flat=True) 1251 values = f"{delimiters[0]}{values}{delimiters[1]}" 1252 elif type_value == exp.DataType.Type.INTERVAL: 1253 nested = f" {interior}" 1254 else: 1255 nested = f"({interior})" 1256 1257 type_sql = f"{type_sql}{nested}{values}" 1258 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1259 exp.DataType.Type.TIMETZ, 1260 exp.DataType.Type.TIMESTAMPTZ, 1261 ): 1262 type_sql = f"{type_sql} WITH TIME ZONE" 1263 1264 return type_sql 1265 1266 def directory_sql(self, expression: exp.Directory) -> str: 1267 local = "LOCAL " if expression.args.get("local") else "" 1268 row_format = self.sql(expression, "row_format") 1269 row_format = f" {row_format}" if row_format else "" 1270 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1271 1272 def delete_sql(self, expression: exp.Delete) -> str: 1273 this = self.sql(expression, "this") 1274 this = f" FROM {this}" if this else "" 1275 using = self.sql(expression, "using") 1276 using = f" USING {using}" if using else "" 1277 where = self.sql(expression, "where") 1278 returning = self.sql(expression, "returning") 1279 limit = self.sql(expression, "limit") 1280 tables = self.expressions(expression, key="tables") 1281 tables = f" {tables}" if tables else "" 1282 if self.RETURNING_END: 1283 expression_sql = f"{this}{using}{where}{returning}{limit}" 1284 else: 1285 expression_sql = f"{returning}{this}{using}{where}{limit}" 1286 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1287 1288 def drop_sql(self, expression: exp.Drop) -> str: 1289 this = self.sql(expression, "this") 1290 expressions = self.expressions(expression, flat=True) 1291 expressions = f" ({expressions})" if expressions else "" 1292 kind = expression.args["kind"] 1293 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1294 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1295 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1296 on_cluster = self.sql(expression, "cluster") 1297 on_cluster = f" {on_cluster}" if on_cluster else "" 1298 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1299 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1300 cascade = " CASCADE" if expression.args.get("cascade") else "" 1301 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1302 purge = " PURGE" if expression.args.get("purge") else "" 1303 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1304 1305 def set_operation(self, expression: exp.SetOperation) -> str: 1306 op_type = type(expression) 1307 op_name = op_type.key.upper() 1308 1309 distinct = expression.args.get("distinct") 1310 if ( 1311 distinct is False 1312 and op_type in (exp.Except, exp.Intersect) 1313 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1314 ): 1315 self.unsupported(f"{op_name} ALL is not supported") 1316 1317 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1318 1319 if distinct is None: 1320 distinct = default_distinct 1321 if distinct is None: 1322 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1323 1324 if distinct is default_distinct: 1325 kind = "" 1326 else: 1327 kind = " DISTINCT" if distinct else " ALL" 1328 1329 by_name = " BY NAME" if expression.args.get("by_name") else "" 1330 return f"{op_name}{kind}{by_name}" 1331 1332 def set_operations(self, expression: exp.SetOperation) -> str: 1333 if not self.SET_OP_MODIFIERS: 1334 limit = expression.args.get("limit") 1335 order = expression.args.get("order") 1336 1337 if limit or order: 1338 select = exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1339 1340 if limit: 1341 select = select.limit(limit.pop(), copy=False) 1342 if order: 1343 select = select.order_by(order.pop(), copy=False) 1344 return self.sql(select) 1345 1346 sqls: t.List[str] = [] 1347 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1348 1349 while stack: 1350 node = stack.pop() 1351 1352 if isinstance(node, exp.SetOperation): 1353 stack.append(node.expression) 1354 stack.append( 1355 self.maybe_comment( 1356 self.set_operation(node), comments=node.comments, separated=True 1357 ) 1358 ) 1359 stack.append(node.this) 1360 else: 1361 sqls.append(self.sql(node)) 1362 1363 this = self.sep().join(sqls) 1364 this = self.query_modifiers(expression, this) 1365 return self.prepend_ctes(expression, this) 1366 1367 def fetch_sql(self, expression: exp.Fetch) -> str: 1368 direction = expression.args.get("direction") 1369 direction = f" {direction}" if direction else "" 1370 count = self.sql(expression, "count") 1371 count = f" {count}" if count else "" 1372 if expression.args.get("percent"): 1373 count = f"{count} PERCENT" 1374 with_ties_or_only = "WITH TIES" if expression.args.get("with_ties") else "ONLY" 1375 return f"{self.seg('FETCH')}{direction}{count} ROWS {with_ties_or_only}" 1376 1377 def filter_sql(self, expression: exp.Filter) -> str: 1378 if self.AGGREGATE_FILTER_SUPPORTED: 1379 this = self.sql(expression, "this") 1380 where = self.sql(expression, "expression").strip() 1381 return f"{this} FILTER({where})" 1382 1383 agg = expression.this 1384 agg_arg = agg.this 1385 cond = expression.expression.this 1386 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1387 return self.sql(agg) 1388 1389 def hint_sql(self, expression: exp.Hint) -> str: 1390 if not self.QUERY_HINTS: 1391 self.unsupported("Hints are not supported") 1392 return "" 1393 1394 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1395 1396 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1397 using = self.sql(expression, "using") 1398 using = f" USING {using}" if using else "" 1399 columns = self.expressions(expression, key="columns", flat=True) 1400 columns = f"({columns})" if columns else "" 1401 partition_by = self.expressions(expression, key="partition_by", flat=True) 1402 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1403 where = self.sql(expression, "where") 1404 include = self.expressions(expression, key="include", flat=True) 1405 if include: 1406 include = f" INCLUDE ({include})" 1407 with_storage = self.expressions(expression, key="with_storage", flat=True) 1408 with_storage = f" WITH ({with_storage})" if with_storage else "" 1409 tablespace = self.sql(expression, "tablespace") 1410 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1411 on = self.sql(expression, "on") 1412 on = f" ON {on}" if on else "" 1413 1414 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1415 1416 def index_sql(self, expression: exp.Index) -> str: 1417 unique = "UNIQUE " if expression.args.get("unique") else "" 1418 primary = "PRIMARY " if expression.args.get("primary") else "" 1419 amp = "AMP " if expression.args.get("amp") else "" 1420 name = self.sql(expression, "this") 1421 name = f"{name} " if name else "" 1422 table = self.sql(expression, "table") 1423 table = f"{self.INDEX_ON} {table}" if table else "" 1424 1425 index = "INDEX " if not table else "" 1426 1427 params = self.sql(expression, "params") 1428 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1429 1430 def identifier_sql(self, expression: exp.Identifier) -> str: 1431 text = expression.name 1432 lower = text.lower() 1433 text = lower if self.normalize and not expression.quoted else text 1434 text = text.replace(self.dialect.IDENTIFIER_END, self._escaped_identifier_end) 1435 if ( 1436 expression.quoted 1437 or self.dialect.can_identify(text, self.identify) 1438 or lower in self.RESERVED_KEYWORDS 1439 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1440 ): 1441 text = f"{self.dialect.IDENTIFIER_START}{text}{self.dialect.IDENTIFIER_END}" 1442 return text 1443 1444 def hex_sql(self, expression: exp.Hex) -> str: 1445 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1446 if self.dialect.HEX_LOWERCASE: 1447 text = self.func("LOWER", text) 1448 1449 return text 1450 1451 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1452 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1453 if not self.dialect.HEX_LOWERCASE: 1454 text = self.func("LOWER", text) 1455 return text 1456 1457 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1458 input_format = self.sql(expression, "input_format") 1459 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1460 output_format = self.sql(expression, "output_format") 1461 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1462 return self.sep().join((input_format, output_format)) 1463 1464 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1465 string = self.sql(exp.Literal.string(expression.name)) 1466 return f"{prefix}{string}" 1467 1468 def partition_sql(self, expression: exp.Partition) -> str: 1469 return f"PARTITION({self.expressions(expression, flat=True)})" 1470 1471 def properties_sql(self, expression: exp.Properties) -> str: 1472 root_properties = [] 1473 with_properties = [] 1474 1475 for p in expression.expressions: 1476 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1477 if p_loc == exp.Properties.Location.POST_WITH: 1478 with_properties.append(p) 1479 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1480 root_properties.append(p) 1481 1482 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1483 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1484 1485 if root_props and with_props and not self.pretty: 1486 with_props = " " + with_props 1487 1488 return root_props + with_props 1489 1490 def root_properties(self, properties: exp.Properties) -> str: 1491 if properties.expressions: 1492 return self.expressions(properties, indent=False, sep=" ") 1493 return "" 1494 1495 def properties( 1496 self, 1497 properties: exp.Properties, 1498 prefix: str = "", 1499 sep: str = ", ", 1500 suffix: str = "", 1501 wrapped: bool = True, 1502 ) -> str: 1503 if properties.expressions: 1504 expressions = self.expressions(properties, sep=sep, indent=False) 1505 if expressions: 1506 expressions = self.wrap(expressions) if wrapped else expressions 1507 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1508 return "" 1509 1510 def with_properties(self, properties: exp.Properties) -> str: 1511 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1512 1513 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1514 properties_locs = defaultdict(list) 1515 for p in properties.expressions: 1516 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1517 if p_loc != exp.Properties.Location.UNSUPPORTED: 1518 properties_locs[p_loc].append(p) 1519 else: 1520 self.unsupported(f"Unsupported property {p.key}") 1521 1522 return properties_locs 1523 1524 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1525 if isinstance(expression.this, exp.Dot): 1526 return self.sql(expression, "this") 1527 return f"'{expression.name}'" if string_key else expression.name 1528 1529 def property_sql(self, expression: exp.Property) -> str: 1530 property_cls = expression.__class__ 1531 if property_cls == exp.Property: 1532 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1533 1534 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1535 if not property_name: 1536 self.unsupported(f"Unsupported property {expression.key}") 1537 1538 return f"{property_name}={self.sql(expression, 'this')}" 1539 1540 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1541 if self.SUPPORTS_CREATE_TABLE_LIKE: 1542 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1543 options = f" {options}" if options else "" 1544 1545 like = f"LIKE {self.sql(expression, 'this')}{options}" 1546 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1547 like = f"({like})" 1548 1549 return like 1550 1551 if expression.expressions: 1552 self.unsupported("Transpilation of LIKE property options is unsupported") 1553 1554 select = exp.select("*").from_(expression.this).limit(0) 1555 return f"AS {self.sql(select)}" 1556 1557 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1558 no = "NO " if expression.args.get("no") else "" 1559 protection = " PROTECTION" if expression.args.get("protection") else "" 1560 return f"{no}FALLBACK{protection}" 1561 1562 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1563 no = "NO " if expression.args.get("no") else "" 1564 local = expression.args.get("local") 1565 local = f"{local} " if local else "" 1566 dual = "DUAL " if expression.args.get("dual") else "" 1567 before = "BEFORE " if expression.args.get("before") else "" 1568 after = "AFTER " if expression.args.get("after") else "" 1569 return f"{no}{local}{dual}{before}{after}JOURNAL" 1570 1571 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1572 freespace = self.sql(expression, "this") 1573 percent = " PERCENT" if expression.args.get("percent") else "" 1574 return f"FREESPACE={freespace}{percent}" 1575 1576 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1577 if expression.args.get("default"): 1578 property = "DEFAULT" 1579 elif expression.args.get("on"): 1580 property = "ON" 1581 else: 1582 property = "OFF" 1583 return f"CHECKSUM={property}" 1584 1585 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1586 if expression.args.get("no"): 1587 return "NO MERGEBLOCKRATIO" 1588 if expression.args.get("default"): 1589 return "DEFAULT MERGEBLOCKRATIO" 1590 1591 percent = " PERCENT" if expression.args.get("percent") else "" 1592 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1593 1594 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1595 default = expression.args.get("default") 1596 minimum = expression.args.get("minimum") 1597 maximum = expression.args.get("maximum") 1598 if default or minimum or maximum: 1599 if default: 1600 prop = "DEFAULT" 1601 elif minimum: 1602 prop = "MINIMUM" 1603 else: 1604 prop = "MAXIMUM" 1605 return f"{prop} DATABLOCKSIZE" 1606 units = expression.args.get("units") 1607 units = f" {units}" if units else "" 1608 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1609 1610 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1611 autotemp = expression.args.get("autotemp") 1612 always = expression.args.get("always") 1613 default = expression.args.get("default") 1614 manual = expression.args.get("manual") 1615 never = expression.args.get("never") 1616 1617 if autotemp is not None: 1618 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1619 elif always: 1620 prop = "ALWAYS" 1621 elif default: 1622 prop = "DEFAULT" 1623 elif manual: 1624 prop = "MANUAL" 1625 elif never: 1626 prop = "NEVER" 1627 return f"BLOCKCOMPRESSION={prop}" 1628 1629 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1630 no = expression.args.get("no") 1631 no = " NO" if no else "" 1632 concurrent = expression.args.get("concurrent") 1633 concurrent = " CONCURRENT" if concurrent else "" 1634 target = self.sql(expression, "target") 1635 target = f" {target}" if target else "" 1636 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1637 1638 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1639 if isinstance(expression.this, list): 1640 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1641 if expression.this: 1642 modulus = self.sql(expression, "this") 1643 remainder = self.sql(expression, "expression") 1644 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1645 1646 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1647 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1648 return f"FROM ({from_expressions}) TO ({to_expressions})" 1649 1650 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1651 this = self.sql(expression, "this") 1652 1653 for_values_or_default = expression.expression 1654 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1655 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1656 else: 1657 for_values_or_default = " DEFAULT" 1658 1659 return f"PARTITION OF {this}{for_values_or_default}" 1660 1661 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1662 kind = expression.args.get("kind") 1663 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1664 for_or_in = expression.args.get("for_or_in") 1665 for_or_in = f" {for_or_in}" if for_or_in else "" 1666 lock_type = expression.args.get("lock_type") 1667 override = " OVERRIDE" if expression.args.get("override") else "" 1668 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1669 1670 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1671 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1672 statistics = expression.args.get("statistics") 1673 statistics_sql = "" 1674 if statistics is not None: 1675 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1676 return f"{data_sql}{statistics_sql}" 1677 1678 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1679 this = self.sql(expression, "this") 1680 this = f"HISTORY_TABLE={this}" if this else "" 1681 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1682 data_consistency = ( 1683 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1684 ) 1685 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1686 retention_period = ( 1687 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1688 ) 1689 1690 if this: 1691 on_sql = self.func("ON", this, data_consistency, retention_period) 1692 else: 1693 on_sql = "ON" if expression.args.get("on") else "OFF" 1694 1695 sql = f"SYSTEM_VERSIONING={on_sql}" 1696 1697 return f"WITH({sql})" if expression.args.get("with") else sql 1698 1699 def insert_sql(self, expression: exp.Insert) -> str: 1700 hint = self.sql(expression, "hint") 1701 overwrite = expression.args.get("overwrite") 1702 1703 if isinstance(expression.this, exp.Directory): 1704 this = " OVERWRITE" if overwrite else " INTO" 1705 else: 1706 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1707 1708 stored = self.sql(expression, "stored") 1709 stored = f" {stored}" if stored else "" 1710 alternative = expression.args.get("alternative") 1711 alternative = f" OR {alternative}" if alternative else "" 1712 ignore = " IGNORE" if expression.args.get("ignore") else "" 1713 is_function = expression.args.get("is_function") 1714 if is_function: 1715 this = f"{this} FUNCTION" 1716 this = f"{this} {self.sql(expression, 'this')}" 1717 1718 exists = " IF EXISTS" if expression.args.get("exists") else "" 1719 where = self.sql(expression, "where") 1720 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1721 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1722 on_conflict = self.sql(expression, "conflict") 1723 on_conflict = f" {on_conflict}" if on_conflict else "" 1724 by_name = " BY NAME" if expression.args.get("by_name") else "" 1725 returning = self.sql(expression, "returning") 1726 1727 if self.RETURNING_END: 1728 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1729 else: 1730 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1731 1732 partition_by = self.sql(expression, "partition") 1733 partition_by = f" {partition_by}" if partition_by else "" 1734 settings = self.sql(expression, "settings") 1735 settings = f" {settings}" if settings else "" 1736 1737 source = self.sql(expression, "source") 1738 source = f"TABLE {source}" if source else "" 1739 1740 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1741 return self.prepend_ctes(expression, sql) 1742 1743 def introducer_sql(self, expression: exp.Introducer) -> str: 1744 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1745 1746 def kill_sql(self, expression: exp.Kill) -> str: 1747 kind = self.sql(expression, "kind") 1748 kind = f" {kind}" if kind else "" 1749 this = self.sql(expression, "this") 1750 this = f" {this}" if this else "" 1751 return f"KILL{kind}{this}" 1752 1753 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1754 return expression.name 1755 1756 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1757 return expression.name 1758 1759 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1760 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1761 1762 constraint = self.sql(expression, "constraint") 1763 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1764 1765 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1766 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1767 action = self.sql(expression, "action") 1768 1769 expressions = self.expressions(expression, flat=True) 1770 if expressions: 1771 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1772 expressions = f" {set_keyword}{expressions}" 1773 1774 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}" 1775 1776 def returning_sql(self, expression: exp.Returning) -> str: 1777 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1778 1779 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1780 fields = self.sql(expression, "fields") 1781 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1782 escaped = self.sql(expression, "escaped") 1783 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1784 items = self.sql(expression, "collection_items") 1785 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1786 keys = self.sql(expression, "map_keys") 1787 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1788 lines = self.sql(expression, "lines") 1789 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1790 null = self.sql(expression, "null") 1791 null = f" NULL DEFINED AS {null}" if null else "" 1792 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1793 1794 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1795 return f"WITH ({self.expressions(expression, flat=True)})" 1796 1797 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1798 this = f"{self.sql(expression, 'this')} INDEX" 1799 target = self.sql(expression, "target") 1800 target = f" FOR {target}" if target else "" 1801 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1802 1803 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1804 this = self.sql(expression, "this") 1805 kind = self.sql(expression, "kind") 1806 expr = self.sql(expression, "expression") 1807 return f"{this} ({kind} => {expr})" 1808 1809 def table_parts(self, expression: exp.Table) -> str: 1810 return ".".join( 1811 self.sql(part) 1812 for part in ( 1813 expression.args.get("catalog"), 1814 expression.args.get("db"), 1815 expression.args.get("this"), 1816 ) 1817 if part is not None 1818 ) 1819 1820 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 1821 table = self.table_parts(expression) 1822 only = "ONLY " if expression.args.get("only") else "" 1823 partition = self.sql(expression, "partition") 1824 partition = f" {partition}" if partition else "" 1825 version = self.sql(expression, "version") 1826 version = f" {version}" if version else "" 1827 alias = self.sql(expression, "alias") 1828 alias = f"{sep}{alias}" if alias else "" 1829 1830 sample = self.sql(expression, "sample") 1831 if self.dialect.ALIAS_POST_TABLESAMPLE: 1832 sample_pre_alias = sample 1833 sample_post_alias = "" 1834 else: 1835 sample_pre_alias = "" 1836 sample_post_alias = sample 1837 1838 hints = self.expressions(expression, key="hints", sep=" ") 1839 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 1840 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 1841 joins = self.indent( 1842 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 1843 ) 1844 laterals = self.expressions(expression, key="laterals", sep="") 1845 1846 file_format = self.sql(expression, "format") 1847 if file_format: 1848 pattern = self.sql(expression, "pattern") 1849 pattern = f", PATTERN => {pattern}" if pattern else "" 1850 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 1851 1852 ordinality = expression.args.get("ordinality") or "" 1853 if ordinality: 1854 ordinality = f" WITH ORDINALITY{alias}" 1855 alias = "" 1856 1857 when = self.sql(expression, "when") 1858 if when: 1859 table = f"{table} {when}" 1860 1861 changes = self.sql(expression, "changes") 1862 changes = f" {changes}" if changes else "" 1863 1864 rows_from = self.expressions(expression, key="rows_from") 1865 if rows_from: 1866 table = f"ROWS FROM {self.wrap(rows_from)}" 1867 1868 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 1869 1870 def tablesample_sql( 1871 self, 1872 expression: exp.TableSample, 1873 tablesample_keyword: t.Optional[str] = None, 1874 ) -> str: 1875 method = self.sql(expression, "method") 1876 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 1877 numerator = self.sql(expression, "bucket_numerator") 1878 denominator = self.sql(expression, "bucket_denominator") 1879 field = self.sql(expression, "bucket_field") 1880 field = f" ON {field}" if field else "" 1881 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 1882 seed = self.sql(expression, "seed") 1883 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 1884 1885 size = self.sql(expression, "size") 1886 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 1887 size = f"{size} ROWS" 1888 1889 percent = self.sql(expression, "percent") 1890 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 1891 percent = f"{percent} PERCENT" 1892 1893 expr = f"{bucket}{percent}{size}" 1894 if self.TABLESAMPLE_REQUIRES_PARENS: 1895 expr = f"({expr})" 1896 1897 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 1898 1899 def pivot_sql(self, expression: exp.Pivot) -> str: 1900 expressions = self.expressions(expression, flat=True) 1901 1902 if expression.this: 1903 this = self.sql(expression, "this") 1904 if not expressions: 1905 return f"UNPIVOT {this}" 1906 1907 on = f"{self.seg('ON')} {expressions}" 1908 using = self.expressions(expression, key="using", flat=True) 1909 using = f"{self.seg('USING')} {using}" if using else "" 1910 group = self.sql(expression, "group") 1911 return f"PIVOT {this}{on}{using}{group}" 1912 1913 alias = self.sql(expression, "alias") 1914 alias = f" AS {alias}" if alias else "" 1915 direction = self.seg("UNPIVOT" if expression.unpivot else "PIVOT") 1916 1917 field = self.sql(expression, "field") 1918 if field and isinstance(expression.args.get("field"), exp.PivotAny): 1919 field = f"IN ({field})" 1920 1921 include_nulls = expression.args.get("include_nulls") 1922 if include_nulls is not None: 1923 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 1924 else: 1925 nulls = "" 1926 1927 default_on_null = self.sql(expression, "default_on_null") 1928 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 1929 return f"{direction}{nulls}({expressions} FOR {field}{default_on_null}){alias}" 1930 1931 def version_sql(self, expression: exp.Version) -> str: 1932 this = f"FOR {expression.name}" 1933 kind = expression.text("kind") 1934 expr = self.sql(expression, "expression") 1935 return f"{this} {kind} {expr}" 1936 1937 def tuple_sql(self, expression: exp.Tuple) -> str: 1938 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 1939 1940 def update_sql(self, expression: exp.Update) -> str: 1941 this = self.sql(expression, "this") 1942 set_sql = self.expressions(expression, flat=True) 1943 from_sql = self.sql(expression, "from") 1944 where_sql = self.sql(expression, "where") 1945 returning = self.sql(expression, "returning") 1946 order = self.sql(expression, "order") 1947 limit = self.sql(expression, "limit") 1948 if self.RETURNING_END: 1949 expression_sql = f"{from_sql}{where_sql}{returning}" 1950 else: 1951 expression_sql = f"{returning}{from_sql}{where_sql}" 1952 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 1953 return self.prepend_ctes(expression, sql) 1954 1955 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 1956 values_as_table = values_as_table and self.VALUES_AS_TABLE 1957 1958 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 1959 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 1960 args = self.expressions(expression) 1961 alias = self.sql(expression, "alias") 1962 values = f"VALUES{self.seg('')}{args}" 1963 values = ( 1964 f"({values})" 1965 if self.WRAP_DERIVED_VALUES and (alias or isinstance(expression.parent, exp.From)) 1966 else values 1967 ) 1968 return f"{values} AS {alias}" if alias else values 1969 1970 # Converts `VALUES...` expression into a series of select unions. 1971 alias_node = expression.args.get("alias") 1972 column_names = alias_node and alias_node.columns 1973 1974 selects: t.List[exp.Query] = [] 1975 1976 for i, tup in enumerate(expression.expressions): 1977 row = tup.expressions 1978 1979 if i == 0 and column_names: 1980 row = [ 1981 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 1982 ] 1983 1984 selects.append(exp.Select(expressions=row)) 1985 1986 if self.pretty: 1987 # This may result in poor performance for large-cardinality `VALUES` tables, due to 1988 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 1989 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 1990 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 1991 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 1992 1993 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 1994 unions = " UNION ALL ".join(self.sql(select) for select in selects) 1995 return f"({unions}){alias}" 1996 1997 def var_sql(self, expression: exp.Var) -> str: 1998 return self.sql(expression, "this") 1999 2000 def into_sql(self, expression: exp.Into) -> str: 2001 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2002 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2003 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2004 2005 def from_sql(self, expression: exp.From) -> str: 2006 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2007 2008 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2009 grouping_sets = self.expressions(expression, indent=False) 2010 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2011 2012 def rollup_sql(self, expression: exp.Rollup) -> str: 2013 expressions = self.expressions(expression, indent=False) 2014 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2015 2016 def cube_sql(self, expression: exp.Cube) -> str: 2017 expressions = self.expressions(expression, indent=False) 2018 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2019 2020 def group_sql(self, expression: exp.Group) -> str: 2021 group_by_all = expression.args.get("all") 2022 if group_by_all is True: 2023 modifier = " ALL" 2024 elif group_by_all is False: 2025 modifier = " DISTINCT" 2026 else: 2027 modifier = "" 2028 2029 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2030 2031 grouping_sets = self.expressions(expression, key="grouping_sets") 2032 cube = self.expressions(expression, key="cube") 2033 rollup = self.expressions(expression, key="rollup") 2034 2035 groupings = csv( 2036 self.seg(grouping_sets) if grouping_sets else "", 2037 self.seg(cube) if cube else "", 2038 self.seg(rollup) if rollup else "", 2039 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2040 sep=self.GROUPINGS_SEP, 2041 ) 2042 2043 if ( 2044 expression.expressions 2045 and groupings 2046 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2047 ): 2048 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2049 2050 return f"{group_by}{groupings}" 2051 2052 def having_sql(self, expression: exp.Having) -> str: 2053 this = self.indent(self.sql(expression, "this")) 2054 return f"{self.seg('HAVING')}{self.sep()}{this}" 2055 2056 def connect_sql(self, expression: exp.Connect) -> str: 2057 start = self.sql(expression, "start") 2058 start = self.seg(f"START WITH {start}") if start else "" 2059 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2060 connect = self.sql(expression, "connect") 2061 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2062 return start + connect 2063 2064 def prior_sql(self, expression: exp.Prior) -> str: 2065 return f"PRIOR {self.sql(expression, 'this')}" 2066 2067 def join_sql(self, expression: exp.Join) -> str: 2068 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2069 side = None 2070 else: 2071 side = expression.side 2072 2073 op_sql = " ".join( 2074 op 2075 for op in ( 2076 expression.method, 2077 "GLOBAL" if expression.args.get("global") else None, 2078 side, 2079 expression.kind, 2080 expression.hint if self.JOIN_HINTS else None, 2081 ) 2082 if op 2083 ) 2084 match_cond = self.sql(expression, "match_condition") 2085 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2086 on_sql = self.sql(expression, "on") 2087 using = expression.args.get("using") 2088 2089 if not on_sql and using: 2090 on_sql = csv(*(self.sql(column) for column in using)) 2091 2092 this = expression.this 2093 this_sql = self.sql(this) 2094 2095 if on_sql: 2096 on_sql = self.indent(on_sql, skip_first=True) 2097 space = self.seg(" " * self.pad) if self.pretty else " " 2098 if using: 2099 on_sql = f"{space}USING ({on_sql})" 2100 else: 2101 on_sql = f"{space}ON {on_sql}" 2102 elif not op_sql: 2103 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2104 return f" {this_sql}" 2105 2106 return f", {this_sql}" 2107 2108 if op_sql != "STRAIGHT_JOIN": 2109 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2110 2111 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}" 2112 2113 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str: 2114 args = self.expressions(expression, flat=True) 2115 args = f"({args})" if len(args.split(",")) > 1 else args 2116 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2117 2118 def lateral_op(self, expression: exp.Lateral) -> str: 2119 cross_apply = expression.args.get("cross_apply") 2120 2121 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2122 if cross_apply is True: 2123 op = "INNER JOIN " 2124 elif cross_apply is False: 2125 op = "LEFT JOIN " 2126 else: 2127 op = "" 2128 2129 return f"{op}LATERAL" 2130 2131 def lateral_sql(self, expression: exp.Lateral) -> str: 2132 this = self.sql(expression, "this") 2133 2134 if expression.args.get("view"): 2135 alias = expression.args["alias"] 2136 columns = self.expressions(alias, key="columns", flat=True) 2137 table = f" {alias.name}" if alias.name else "" 2138 columns = f" AS {columns}" if columns else "" 2139 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2140 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2141 2142 alias = self.sql(expression, "alias") 2143 alias = f" AS {alias}" if alias else "" 2144 return f"{self.lateral_op(expression)} {this}{alias}" 2145 2146 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2147 this = self.sql(expression, "this") 2148 2149 args = [ 2150 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2151 for e in (expression.args.get(k) for k in ("offset", "expression")) 2152 if e 2153 ] 2154 2155 args_sql = ", ".join(self.sql(e) for e in args) 2156 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2157 expressions = self.expressions(expression, flat=True) 2158 expressions = f" BY {expressions}" if expressions else "" 2159 2160 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{expressions}" 2161 2162 def offset_sql(self, expression: exp.Offset) -> str: 2163 this = self.sql(expression, "this") 2164 value = expression.expression 2165 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2166 expressions = self.expressions(expression, flat=True) 2167 expressions = f" BY {expressions}" if expressions else "" 2168 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2169 2170 def setitem_sql(self, expression: exp.SetItem) -> str: 2171 kind = self.sql(expression, "kind") 2172 kind = f"{kind} " if kind else "" 2173 this = self.sql(expression, "this") 2174 expressions = self.expressions(expression) 2175 collate = self.sql(expression, "collate") 2176 collate = f" COLLATE {collate}" if collate else "" 2177 global_ = "GLOBAL " if expression.args.get("global") else "" 2178 return f"{global_}{kind}{this}{expressions}{collate}" 2179 2180 def set_sql(self, expression: exp.Set) -> str: 2181 expressions = ( 2182 f" {self.expressions(expression, flat=True)}" if expression.expressions else "" 2183 ) 2184 tag = " TAG" if expression.args.get("tag") else "" 2185 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2186 2187 def pragma_sql(self, expression: exp.Pragma) -> str: 2188 return f"PRAGMA {self.sql(expression, 'this')}" 2189 2190 def lock_sql(self, expression: exp.Lock) -> str: 2191 if not self.LOCKING_READS_SUPPORTED: 2192 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2193 return "" 2194 2195 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 2196 expressions = self.expressions(expression, flat=True) 2197 expressions = f" OF {expressions}" if expressions else "" 2198 wait = expression.args.get("wait") 2199 2200 if wait is not None: 2201 if isinstance(wait, exp.Literal): 2202 wait = f" WAIT {self.sql(wait)}" 2203 else: 2204 wait = " NOWAIT" if wait else " SKIP LOCKED" 2205 2206 return f"{lock_type}{expressions}{wait or ''}" 2207 2208 def literal_sql(self, expression: exp.Literal) -> str: 2209 text = expression.this or "" 2210 if expression.is_string: 2211 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2212 return text 2213 2214 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2215 if self.dialect.ESCAPED_SEQUENCES: 2216 to_escaped = self.dialect.ESCAPED_SEQUENCES 2217 text = "".join( 2218 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2219 ) 2220 2221 return self._replace_line_breaks(text).replace( 2222 self.dialect.QUOTE_END, self._escaped_quote_end 2223 ) 2224 2225 def loaddata_sql(self, expression: exp.LoadData) -> str: 2226 local = " LOCAL" if expression.args.get("local") else "" 2227 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2228 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2229 this = f" INTO TABLE {self.sql(expression, 'this')}" 2230 partition = self.sql(expression, "partition") 2231 partition = f" {partition}" if partition else "" 2232 input_format = self.sql(expression, "input_format") 2233 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2234 serde = self.sql(expression, "serde") 2235 serde = f" SERDE {serde}" if serde else "" 2236 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2237 2238 def null_sql(self, *_) -> str: 2239 return "NULL" 2240 2241 def boolean_sql(self, expression: exp.Boolean) -> str: 2242 return "TRUE" if expression.this else "FALSE" 2243 2244 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2245 this = self.sql(expression, "this") 2246 this = f"{this} " if this else this 2247 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2248 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2249 2250 def withfill_sql(self, expression: exp.WithFill) -> str: 2251 from_sql = self.sql(expression, "from") 2252 from_sql = f" FROM {from_sql}" if from_sql else "" 2253 to_sql = self.sql(expression, "to") 2254 to_sql = f" TO {to_sql}" if to_sql else "" 2255 step_sql = self.sql(expression, "step") 2256 step_sql = f" STEP {step_sql}" if step_sql else "" 2257 interpolated_values = [ 2258 f"{self.sql(named_expression, 'alias')} AS {self.sql(named_expression, 'this')}" 2259 for named_expression in expression.args.get("interpolate") or [] 2260 ] 2261 interpolate = ( 2262 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2263 ) 2264 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2265 2266 def cluster_sql(self, expression: exp.Cluster) -> str: 2267 return self.op_expressions("CLUSTER BY", expression) 2268 2269 def distribute_sql(self, expression: exp.Distribute) -> str: 2270 return self.op_expressions("DISTRIBUTE BY", expression) 2271 2272 def sort_sql(self, expression: exp.Sort) -> str: 2273 return self.op_expressions("SORT BY", expression) 2274 2275 def ordered_sql(self, expression: exp.Ordered) -> str: 2276 desc = expression.args.get("desc") 2277 asc = not desc 2278 2279 nulls_first = expression.args.get("nulls_first") 2280 nulls_last = not nulls_first 2281 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2282 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2283 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2284 2285 this = self.sql(expression, "this") 2286 2287 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2288 nulls_sort_change = "" 2289 if nulls_first and ( 2290 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2291 ): 2292 nulls_sort_change = " NULLS FIRST" 2293 elif ( 2294 nulls_last 2295 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2296 and not nulls_are_last 2297 ): 2298 nulls_sort_change = " NULLS LAST" 2299 2300 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2301 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2302 window = expression.find_ancestor(exp.Window, exp.Select) 2303 if isinstance(window, exp.Window) and window.args.get("spec"): 2304 self.unsupported( 2305 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2306 ) 2307 nulls_sort_change = "" 2308 elif self.NULL_ORDERING_SUPPORTED is None: 2309 if expression.this.is_int: 2310 self.unsupported( 2311 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2312 ) 2313 elif not isinstance(expression.this, exp.Rand): 2314 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2315 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2316 nulls_sort_change = "" 2317 2318 with_fill = self.sql(expression, "with_fill") 2319 with_fill = f" {with_fill}" if with_fill else "" 2320 2321 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2322 2323 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2324 window_frame = self.sql(expression, "window_frame") 2325 window_frame = f"{window_frame} " if window_frame else "" 2326 2327 this = self.sql(expression, "this") 2328 2329 return f"{window_frame}{this}" 2330 2331 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2332 partition = self.partition_by_sql(expression) 2333 order = self.sql(expression, "order") 2334 measures = self.expressions(expression, key="measures") 2335 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2336 rows = self.sql(expression, "rows") 2337 rows = self.seg(rows) if rows else "" 2338 after = self.sql(expression, "after") 2339 after = self.seg(after) if after else "" 2340 pattern = self.sql(expression, "pattern") 2341 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2342 definition_sqls = [ 2343 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2344 for definition in expression.args.get("define", []) 2345 ] 2346 definitions = self.expressions(sqls=definition_sqls) 2347 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2348 body = "".join( 2349 ( 2350 partition, 2351 order, 2352 measures, 2353 rows, 2354 after, 2355 pattern, 2356 define, 2357 ) 2358 ) 2359 alias = self.sql(expression, "alias") 2360 alias = f" {alias}" if alias else "" 2361 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2362 2363 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2364 limit = expression.args.get("limit") 2365 2366 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2367 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2368 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2369 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2370 2371 return csv( 2372 *sqls, 2373 *[self.sql(join) for join in expression.args.get("joins") or []], 2374 self.sql(expression, "connect"), 2375 self.sql(expression, "match"), 2376 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2377 self.sql(expression, "prewhere"), 2378 self.sql(expression, "where"), 2379 self.sql(expression, "group"), 2380 self.sql(expression, "having"), 2381 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2382 self.sql(expression, "order"), 2383 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2384 *self.after_limit_modifiers(expression), 2385 self.options_modifier(expression), 2386 sep="", 2387 ) 2388 2389 def options_modifier(self, expression: exp.Expression) -> str: 2390 options = self.expressions(expression, key="options") 2391 return f" {options}" if options else "" 2392 2393 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2394 return "" 2395 2396 def offset_limit_modifiers( 2397 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2398 ) -> t.List[str]: 2399 return [ 2400 self.sql(expression, "offset") if fetch else self.sql(limit), 2401 self.sql(limit) if fetch else self.sql(expression, "offset"), 2402 ] 2403 2404 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2405 locks = self.expressions(expression, key="locks", sep=" ") 2406 locks = f" {locks}" if locks else "" 2407 return [locks, self.sql(expression, "sample")] 2408 2409 def select_sql(self, expression: exp.Select) -> str: 2410 into = expression.args.get("into") 2411 if not self.SUPPORTS_SELECT_INTO and into: 2412 into.pop() 2413 2414 hint = self.sql(expression, "hint") 2415 distinct = self.sql(expression, "distinct") 2416 distinct = f" {distinct}" if distinct else "" 2417 kind = self.sql(expression, "kind") 2418 2419 limit = expression.args.get("limit") 2420 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2421 top = self.limit_sql(limit, top=True) 2422 limit.pop() 2423 else: 2424 top = "" 2425 2426 expressions = self.expressions(expression) 2427 2428 if kind: 2429 if kind in self.SELECT_KINDS: 2430 kind = f" AS {kind}" 2431 else: 2432 if kind == "STRUCT": 2433 expressions = self.expressions( 2434 sqls=[ 2435 self.sql( 2436 exp.Struct( 2437 expressions=[ 2438 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2439 if isinstance(e, exp.Alias) 2440 else e 2441 for e in expression.expressions 2442 ] 2443 ) 2444 ) 2445 ] 2446 ) 2447 kind = "" 2448 2449 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2450 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2451 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2452 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2453 sql = self.query_modifiers( 2454 expression, 2455 f"SELECT{top_distinct}{kind}{expressions}", 2456 self.sql(expression, "into", comment=False), 2457 self.sql(expression, "from", comment=False), 2458 ) 2459 2460 sql = self.prepend_ctes(expression, sql) 2461 2462 if not self.SUPPORTS_SELECT_INTO and into: 2463 if into.args.get("temporary"): 2464 table_kind = " TEMPORARY" 2465 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2466 table_kind = " UNLOGGED" 2467 else: 2468 table_kind = "" 2469 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2470 2471 return sql 2472 2473 def schema_sql(self, expression: exp.Schema) -> str: 2474 this = self.sql(expression, "this") 2475 sql = self.schema_columns_sql(expression) 2476 return f"{this} {sql}" if this and sql else this or sql 2477 2478 def schema_columns_sql(self, expression: exp.Schema) -> str: 2479 if expression.expressions: 2480 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2481 return "" 2482 2483 def star_sql(self, expression: exp.Star) -> str: 2484 except_ = self.expressions(expression, key="except", flat=True) 2485 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2486 replace = self.expressions(expression, key="replace", flat=True) 2487 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2488 rename = self.expressions(expression, key="rename", flat=True) 2489 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2490 return f"*{except_}{replace}{rename}" 2491 2492 def parameter_sql(self, expression: exp.Parameter) -> str: 2493 this = self.sql(expression, "this") 2494 return f"{self.PARAMETER_TOKEN}{this}" 2495 2496 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2497 this = self.sql(expression, "this") 2498 kind = expression.text("kind") 2499 if kind: 2500 kind = f"{kind}." 2501 return f"@@{kind}{this}" 2502 2503 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2504 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2505 2506 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2507 alias = self.sql(expression, "alias") 2508 alias = f"{sep}{alias}" if alias else "" 2509 sample = self.sql(expression, "sample") 2510 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2511 alias = f"{sample}{alias}" 2512 2513 # Set to None so it's not generated again by self.query_modifiers() 2514 expression.set("sample", None) 2515 2516 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2517 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2518 return self.prepend_ctes(expression, sql) 2519 2520 def qualify_sql(self, expression: exp.Qualify) -> str: 2521 this = self.indent(self.sql(expression, "this")) 2522 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2523 2524 def unnest_sql(self, expression: exp.Unnest) -> str: 2525 args = self.expressions(expression, flat=True) 2526 2527 alias = expression.args.get("alias") 2528 offset = expression.args.get("offset") 2529 2530 if self.UNNEST_WITH_ORDINALITY: 2531 if alias and isinstance(offset, exp.Expression): 2532 alias.append("columns", offset) 2533 2534 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2535 columns = alias.columns 2536 alias = self.sql(columns[0]) if columns else "" 2537 else: 2538 alias = self.sql(alias) 2539 2540 alias = f" AS {alias}" if alias else alias 2541 if self.UNNEST_WITH_ORDINALITY: 2542 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2543 else: 2544 if isinstance(offset, exp.Expression): 2545 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2546 elif offset: 2547 suffix = f"{alias} WITH OFFSET" 2548 else: 2549 suffix = alias 2550 2551 return f"UNNEST({args}){suffix}" 2552 2553 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2554 return "" 2555 2556 def where_sql(self, expression: exp.Where) -> str: 2557 this = self.indent(self.sql(expression, "this")) 2558 return f"{self.seg('WHERE')}{self.sep()}{this}" 2559 2560 def window_sql(self, expression: exp.Window) -> str: 2561 this = self.sql(expression, "this") 2562 partition = self.partition_by_sql(expression) 2563 order = expression.args.get("order") 2564 order = self.order_sql(order, flat=True) if order else "" 2565 spec = self.sql(expression, "spec") 2566 alias = self.sql(expression, "alias") 2567 over = self.sql(expression, "over") or "OVER" 2568 2569 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2570 2571 first = expression.args.get("first") 2572 if first is None: 2573 first = "" 2574 else: 2575 first = "FIRST" if first else "LAST" 2576 2577 if not partition and not order and not spec and alias: 2578 return f"{this} {alias}" 2579 2580 args = " ".join(arg for arg in (alias, first, partition, order, spec) if arg) 2581 return f"{this} ({args})" 2582 2583 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2584 partition = self.expressions(expression, key="partition_by", flat=True) 2585 return f"PARTITION BY {partition}" if partition else "" 2586 2587 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2588 kind = self.sql(expression, "kind") 2589 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2590 end = ( 2591 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2592 or "CURRENT ROW" 2593 ) 2594 return f"{kind} BETWEEN {start} AND {end}" 2595 2596 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2597 this = self.sql(expression, "this") 2598 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2599 return f"{this} WITHIN GROUP ({expression_sql})" 2600 2601 def between_sql(self, expression: exp.Between) -> str: 2602 this = self.sql(expression, "this") 2603 low = self.sql(expression, "low") 2604 high = self.sql(expression, "high") 2605 return f"{this} BETWEEN {low} AND {high}" 2606 2607 def bracket_offset_expressions(self, expression: exp.Bracket) -> t.List[exp.Expression]: 2608 return apply_index_offset( 2609 expression.this, 2610 expression.expressions, 2611 self.dialect.INDEX_OFFSET - expression.args.get("offset", 0), 2612 ) 2613 2614 def bracket_sql(self, expression: exp.Bracket) -> str: 2615 expressions = self.bracket_offset_expressions(expression) 2616 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2617 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2618 2619 def all_sql(self, expression: exp.All) -> str: 2620 return f"ALL {self.wrap(expression)}" 2621 2622 def any_sql(self, expression: exp.Any) -> str: 2623 this = self.sql(expression, "this") 2624 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2625 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2626 this = self.wrap(this) 2627 return f"ANY{this}" 2628 return f"ANY {this}" 2629 2630 def exists_sql(self, expression: exp.Exists) -> str: 2631 return f"EXISTS{self.wrap(expression)}" 2632 2633 def case_sql(self, expression: exp.Case) -> str: 2634 this = self.sql(expression, "this") 2635 statements = [f"CASE {this}" if this else "CASE"] 2636 2637 for e in expression.args["ifs"]: 2638 statements.append(f"WHEN {self.sql(e, 'this')}") 2639 statements.append(f"THEN {self.sql(e, 'true')}") 2640 2641 default = self.sql(expression, "default") 2642 2643 if default: 2644 statements.append(f"ELSE {default}") 2645 2646 statements.append("END") 2647 2648 if self.pretty and self.too_wide(statements): 2649 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2650 2651 return " ".join(statements) 2652 2653 def constraint_sql(self, expression: exp.Constraint) -> str: 2654 this = self.sql(expression, "this") 2655 expressions = self.expressions(expression, flat=True) 2656 return f"CONSTRAINT {this} {expressions}" 2657 2658 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2659 order = expression.args.get("order") 2660 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2661 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2662 2663 def extract_sql(self, expression: exp.Extract) -> str: 2664 this = self.sql(expression, "this") if self.EXTRACT_ALLOWS_QUOTES else expression.this.name 2665 expression_sql = self.sql(expression, "expression") 2666 return f"EXTRACT({this} FROM {expression_sql})" 2667 2668 def trim_sql(self, expression: exp.Trim) -> str: 2669 trim_type = self.sql(expression, "position") 2670 2671 if trim_type == "LEADING": 2672 func_name = "LTRIM" 2673 elif trim_type == "TRAILING": 2674 func_name = "RTRIM" 2675 else: 2676 func_name = "TRIM" 2677 2678 return self.func(func_name, expression.this, expression.expression) 2679 2680 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2681 args = expression.expressions 2682 if isinstance(expression, exp.ConcatWs): 2683 args = args[1:] # Skip the delimiter 2684 2685 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2686 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2687 2688 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2689 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2690 2691 return args 2692 2693 def concat_sql(self, expression: exp.Concat) -> str: 2694 expressions = self.convert_concat_args(expression) 2695 2696 # Some dialects don't allow a single-argument CONCAT call 2697 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2698 return self.sql(expressions[0]) 2699 2700 return self.func("CONCAT", *expressions) 2701 2702 def concatws_sql(self, expression: exp.ConcatWs) -> str: 2703 return self.func( 2704 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 2705 ) 2706 2707 def check_sql(self, expression: exp.Check) -> str: 2708 this = self.sql(expression, key="this") 2709 return f"CHECK ({this})" 2710 2711 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2712 expressions = self.expressions(expression, flat=True) 2713 reference = self.sql(expression, "reference") 2714 reference = f" {reference}" if reference else "" 2715 delete = self.sql(expression, "delete") 2716 delete = f" ON DELETE {delete}" if delete else "" 2717 update = self.sql(expression, "update") 2718 update = f" ON UPDATE {update}" if update else "" 2719 return f"FOREIGN KEY ({expressions}){reference}{delete}{update}" 2720 2721 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2722 expressions = self.expressions(expression, flat=True) 2723 options = self.expressions(expression, key="options", flat=True, sep=" ") 2724 options = f" {options}" if options else "" 2725 return f"PRIMARY KEY ({expressions}){options}" 2726 2727 def if_sql(self, expression: exp.If) -> str: 2728 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 2729 2730 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 2731 modifier = expression.args.get("modifier") 2732 modifier = f" {modifier}" if modifier else "" 2733 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 2734 2735 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 2736 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 2737 2738 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 2739 path = self.expressions(expression, sep="", flat=True).lstrip(".") 2740 if self.QUOTE_JSON_PATH: 2741 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 2742 2743 return path 2744 2745 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 2746 if isinstance(expression, exp.JSONPathPart): 2747 transform = self.TRANSFORMS.get(expression.__class__) 2748 if not callable(transform): 2749 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 2750 return "" 2751 2752 return transform(self, expression) 2753 2754 if isinstance(expression, int): 2755 return str(expression) 2756 2757 if self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 2758 escaped = expression.replace("'", "\\'") 2759 escaped = f"\\'{expression}\\'" 2760 else: 2761 escaped = expression.replace('"', '\\"') 2762 escaped = f'"{escaped}"' 2763 2764 return escaped 2765 2766 def formatjson_sql(self, expression: exp.FormatJson) -> str: 2767 return f"{self.sql(expression, 'this')} FORMAT JSON" 2768 2769 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 2770 null_handling = expression.args.get("null_handling") 2771 null_handling = f" {null_handling}" if null_handling else "" 2772 2773 unique_keys = expression.args.get("unique_keys") 2774 if unique_keys is not None: 2775 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 2776 else: 2777 unique_keys = "" 2778 2779 return_type = self.sql(expression, "return_type") 2780 return_type = f" RETURNING {return_type}" if return_type else "" 2781 encoding = self.sql(expression, "encoding") 2782 encoding = f" ENCODING {encoding}" if encoding else "" 2783 2784 return self.func( 2785 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 2786 *expression.expressions, 2787 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 2788 ) 2789 2790 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 2791 return self.jsonobject_sql(expression) 2792 2793 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 2794 null_handling = expression.args.get("null_handling") 2795 null_handling = f" {null_handling}" if null_handling else "" 2796 return_type = self.sql(expression, "return_type") 2797 return_type = f" RETURNING {return_type}" if return_type else "" 2798 strict = " STRICT" if expression.args.get("strict") else "" 2799 return self.func( 2800 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 2801 ) 2802 2803 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 2804 this = self.sql(expression, "this") 2805 order = self.sql(expression, "order") 2806 null_handling = expression.args.get("null_handling") 2807 null_handling = f" {null_handling}" if null_handling else "" 2808 return_type = self.sql(expression, "return_type") 2809 return_type = f" RETURNING {return_type}" if return_type else "" 2810 strict = " STRICT" if expression.args.get("strict") else "" 2811 return self.func( 2812 "JSON_ARRAYAGG", 2813 this, 2814 suffix=f"{order}{null_handling}{return_type}{strict})", 2815 ) 2816 2817 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 2818 path = self.sql(expression, "path") 2819 path = f" PATH {path}" if path else "" 2820 nested_schema = self.sql(expression, "nested_schema") 2821 2822 if nested_schema: 2823 return f"NESTED{path} {nested_schema}" 2824 2825 this = self.sql(expression, "this") 2826 kind = self.sql(expression, "kind") 2827 kind = f" {kind}" if kind else "" 2828 return f"{this}{kind}{path}" 2829 2830 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 2831 return self.func("COLUMNS", *expression.expressions) 2832 2833 def jsontable_sql(self, expression: exp.JSONTable) -> str: 2834 this = self.sql(expression, "this") 2835 path = self.sql(expression, "path") 2836 path = f", {path}" if path else "" 2837 error_handling = expression.args.get("error_handling") 2838 error_handling = f" {error_handling}" if error_handling else "" 2839 empty_handling = expression.args.get("empty_handling") 2840 empty_handling = f" {empty_handling}" if empty_handling else "" 2841 schema = self.sql(expression, "schema") 2842 return self.func( 2843 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 2844 ) 2845 2846 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 2847 this = self.sql(expression, "this") 2848 kind = self.sql(expression, "kind") 2849 path = self.sql(expression, "path") 2850 path = f" {path}" if path else "" 2851 as_json = " AS JSON" if expression.args.get("as_json") else "" 2852 return f"{this} {kind}{path}{as_json}" 2853 2854 def openjson_sql(self, expression: exp.OpenJSON) -> str: 2855 this = self.sql(expression, "this") 2856 path = self.sql(expression, "path") 2857 path = f", {path}" if path else "" 2858 expressions = self.expressions(expression) 2859 with_ = ( 2860 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 2861 if expressions 2862 else "" 2863 ) 2864 return f"OPENJSON({this}{path}){with_}" 2865 2866 def in_sql(self, expression: exp.In) -> str: 2867 query = expression.args.get("query") 2868 unnest = expression.args.get("unnest") 2869 field = expression.args.get("field") 2870 is_global = " GLOBAL" if expression.args.get("is_global") else "" 2871 2872 if query: 2873 in_sql = self.sql(query) 2874 elif unnest: 2875 in_sql = self.in_unnest_op(unnest) 2876 elif field: 2877 in_sql = self.sql(field) 2878 else: 2879 in_sql = f"({self.expressions(expression, flat=True)})" 2880 2881 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 2882 2883 def in_unnest_op(self, unnest: exp.Unnest) -> str: 2884 return f"(SELECT {self.sql(unnest)})" 2885 2886 def interval_sql(self, expression: exp.Interval) -> str: 2887 unit = self.sql(expression, "unit") 2888 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 2889 unit = self.TIME_PART_SINGULARS.get(unit, unit) 2890 unit = f" {unit}" if unit else "" 2891 2892 if self.SINGLE_STRING_INTERVAL: 2893 this = expression.this.name if expression.this else "" 2894 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 2895 2896 this = self.sql(expression, "this") 2897 if this: 2898 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 2899 this = f" {this}" if unwrapped else f" ({this})" 2900 2901 return f"INTERVAL{this}{unit}" 2902 2903 def return_sql(self, expression: exp.Return) -> str: 2904 return f"RETURN {self.sql(expression, 'this')}" 2905 2906 def reference_sql(self, expression: exp.Reference) -> str: 2907 this = self.sql(expression, "this") 2908 expressions = self.expressions(expression, flat=True) 2909 expressions = f"({expressions})" if expressions else "" 2910 options = self.expressions(expression, key="options", flat=True, sep=" ") 2911 options = f" {options}" if options else "" 2912 return f"REFERENCES {this}{expressions}{options}" 2913 2914 def anonymous_sql(self, expression: exp.Anonymous) -> str: 2915 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 2916 parent = expression.parent 2917 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 2918 return self.func( 2919 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 2920 ) 2921 2922 def paren_sql(self, expression: exp.Paren) -> str: 2923 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 2924 return f"({sql}{self.seg(')', sep='')}" 2925 2926 def neg_sql(self, expression: exp.Neg) -> str: 2927 # This makes sure we don't convert "- - 5" to "--5", which is a comment 2928 this_sql = self.sql(expression, "this") 2929 sep = " " if this_sql[0] == "-" else "" 2930 return f"-{sep}{this_sql}" 2931 2932 def not_sql(self, expression: exp.Not) -> str: 2933 return f"NOT {self.sql(expression, 'this')}" 2934 2935 def alias_sql(self, expression: exp.Alias) -> str: 2936 alias = self.sql(expression, "alias") 2937 alias = f" AS {alias}" if alias else "" 2938 return f"{self.sql(expression, 'this')}{alias}" 2939 2940 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 2941 alias = expression.args["alias"] 2942 2943 identifier_alias = isinstance(alias, exp.Identifier) 2944 literal_alias = isinstance(alias, exp.Literal) 2945 2946 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 2947 alias.replace(exp.Literal.string(alias.output_name)) 2948 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 2949 alias.replace(exp.to_identifier(alias.output_name)) 2950 2951 return self.alias_sql(expression) 2952 2953 def aliases_sql(self, expression: exp.Aliases) -> str: 2954 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 2955 2956 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 2957 this = self.sql(expression, "this") 2958 index = self.sql(expression, "expression") 2959 return f"{this} AT {index}" 2960 2961 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 2962 this = self.sql(expression, "this") 2963 zone = self.sql(expression, "zone") 2964 return f"{this} AT TIME ZONE {zone}" 2965 2966 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 2967 this = self.sql(expression, "this") 2968 zone = self.sql(expression, "zone") 2969 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 2970 2971 def add_sql(self, expression: exp.Add) -> str: 2972 return self.binary(expression, "+") 2973 2974 def and_sql( 2975 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 2976 ) -> str: 2977 return self.connector_sql(expression, "AND", stack) 2978 2979 def or_sql( 2980 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 2981 ) -> str: 2982 return self.connector_sql(expression, "OR", stack) 2983 2984 def xor_sql( 2985 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 2986 ) -> str: 2987 return self.connector_sql(expression, "XOR", stack) 2988 2989 def connector_sql( 2990 self, 2991 expression: exp.Connector, 2992 op: str, 2993 stack: t.Optional[t.List[str | exp.Expression]] = None, 2994 ) -> str: 2995 if stack is not None: 2996 if expression.expressions: 2997 stack.append(self.expressions(expression, sep=f" {op} ")) 2998 else: 2999 stack.append(expression.right) 3000 if expression.comments and self.comments: 3001 for comment in expression.comments: 3002 if comment: 3003 op += f" /*{self.pad_comment(comment)}*/" 3004 stack.extend((op, expression.left)) 3005 return op 3006 3007 stack = [expression] 3008 sqls: t.List[str] = [] 3009 ops = set() 3010 3011 while stack: 3012 node = stack.pop() 3013 if isinstance(node, exp.Connector): 3014 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3015 else: 3016 sql = self.sql(node) 3017 if sqls and sqls[-1] in ops: 3018 sqls[-1] += f" {sql}" 3019 else: 3020 sqls.append(sql) 3021 3022 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3023 return sep.join(sqls) 3024 3025 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3026 return self.binary(expression, "&") 3027 3028 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3029 return self.binary(expression, "<<") 3030 3031 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3032 return f"~{self.sql(expression, 'this')}" 3033 3034 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3035 return self.binary(expression, "|") 3036 3037 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3038 return self.binary(expression, ">>") 3039 3040 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3041 return self.binary(expression, "^") 3042 3043 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3044 format_sql = self.sql(expression, "format") 3045 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3046 to_sql = self.sql(expression, "to") 3047 to_sql = f" {to_sql}" if to_sql else "" 3048 action = self.sql(expression, "action") 3049 action = f" {action}" if action else "" 3050 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{format_sql}{action})" 3051 3052 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3053 zone = self.sql(expression, "this") 3054 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3055 3056 def collate_sql(self, expression: exp.Collate) -> str: 3057 if self.COLLATE_IS_FUNC: 3058 return self.function_fallback_sql(expression) 3059 return self.binary(expression, "COLLATE") 3060 3061 def command_sql(self, expression: exp.Command) -> str: 3062 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3063 3064 def comment_sql(self, expression: exp.Comment) -> str: 3065 this = self.sql(expression, "this") 3066 kind = expression.args["kind"] 3067 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3068 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3069 expression_sql = self.sql(expression, "expression") 3070 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3071 3072 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3073 this = self.sql(expression, "this") 3074 delete = " DELETE" if expression.args.get("delete") else "" 3075 recompress = self.sql(expression, "recompress") 3076 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3077 to_disk = self.sql(expression, "to_disk") 3078 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3079 to_volume = self.sql(expression, "to_volume") 3080 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3081 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3082 3083 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3084 where = self.sql(expression, "where") 3085 group = self.sql(expression, "group") 3086 aggregates = self.expressions(expression, key="aggregates") 3087 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3088 3089 if not (where or group or aggregates) and len(expression.expressions) == 1: 3090 return f"TTL {self.expressions(expression, flat=True)}" 3091 3092 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3093 3094 def transaction_sql(self, expression: exp.Transaction) -> str: 3095 return "BEGIN" 3096 3097 def commit_sql(self, expression: exp.Commit) -> str: 3098 chain = expression.args.get("chain") 3099 if chain is not None: 3100 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3101 3102 return f"COMMIT{chain or ''}" 3103 3104 def rollback_sql(self, expression: exp.Rollback) -> str: 3105 savepoint = expression.args.get("savepoint") 3106 savepoint = f" TO {savepoint}" if savepoint else "" 3107 return f"ROLLBACK{savepoint}" 3108 3109 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3110 this = self.sql(expression, "this") 3111 3112 dtype = self.sql(expression, "dtype") 3113 if dtype: 3114 collate = self.sql(expression, "collate") 3115 collate = f" COLLATE {collate}" if collate else "" 3116 using = self.sql(expression, "using") 3117 using = f" USING {using}" if using else "" 3118 return f"ALTER COLUMN {this} SET DATA TYPE {dtype}{collate}{using}" 3119 3120 default = self.sql(expression, "default") 3121 if default: 3122 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3123 3124 comment = self.sql(expression, "comment") 3125 if comment: 3126 return f"ALTER COLUMN {this} COMMENT {comment}" 3127 3128 allow_null = expression.args.get("allow_null") 3129 drop = expression.args.get("drop") 3130 3131 if not drop and not allow_null: 3132 self.unsupported("Unsupported ALTER COLUMN syntax") 3133 3134 if allow_null is not None: 3135 keyword = "DROP" if drop else "SET" 3136 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3137 3138 return f"ALTER COLUMN {this} DROP DEFAULT" 3139 3140 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3141 this = self.sql(expression, "this") 3142 if not isinstance(expression.this, exp.Var): 3143 this = f"KEY DISTKEY {this}" 3144 return f"ALTER DISTSTYLE {this}" 3145 3146 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3147 compound = " COMPOUND" if expression.args.get("compound") else "" 3148 this = self.sql(expression, "this") 3149 expressions = self.expressions(expression, flat=True) 3150 expressions = f"({expressions})" if expressions else "" 3151 return f"ALTER{compound} SORTKEY {this or expressions}" 3152 3153 def renametable_sql(self, expression: exp.RenameTable) -> str: 3154 if not self.RENAME_TABLE_WITH_DB: 3155 # Remove db from tables 3156 expression = expression.transform( 3157 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3158 ).assert_is(exp.RenameTable) 3159 this = self.sql(expression, "this") 3160 return f"RENAME TO {this}" 3161 3162 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3163 exists = " IF EXISTS" if expression.args.get("exists") else "" 3164 old_column = self.sql(expression, "this") 3165 new_column = self.sql(expression, "to") 3166 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3167 3168 def alterset_sql(self, expression: exp.AlterSet) -> str: 3169 exprs = self.expressions(expression, flat=True) 3170 return f"SET {exprs}" 3171 3172 def alter_sql(self, expression: exp.Alter) -> str: 3173 actions = expression.args["actions"] 3174 3175 if isinstance(actions[0], exp.ColumnDef): 3176 actions = self.add_column_sql(expression) 3177 elif isinstance(actions[0], exp.Schema): 3178 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ") 3179 elif isinstance(actions[0], exp.Delete): 3180 actions = self.expressions(expression, key="actions", flat=True) 3181 elif isinstance(actions[0], exp.Query): 3182 actions = "AS " + self.expressions(expression, key="actions") 3183 else: 3184 actions = self.expressions(expression, key="actions", flat=True) 3185 3186 exists = " IF EXISTS" if expression.args.get("exists") else "" 3187 on_cluster = self.sql(expression, "cluster") 3188 on_cluster = f" {on_cluster}" if on_cluster else "" 3189 only = " ONLY" if expression.args.get("only") else "" 3190 options = self.expressions(expression, key="options") 3191 options = f", {options}" if options else "" 3192 kind = self.sql(expression, "kind") 3193 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster} {actions}{options}" 3194 3195 def add_column_sql(self, expression: exp.Alter) -> str: 3196 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3197 return self.expressions( 3198 expression, 3199 key="actions", 3200 prefix="ADD COLUMN ", 3201 skip_first=True, 3202 ) 3203 return f"ADD {self.expressions(expression, key='actions', flat=True)}" 3204 3205 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3206 expressions = self.expressions(expression) 3207 exists = " IF EXISTS " if expression.args.get("exists") else " " 3208 return f"DROP{exists}{expressions}" 3209 3210 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3211 return f"ADD {self.expressions(expression)}" 3212 3213 def distinct_sql(self, expression: exp.Distinct) -> str: 3214 this = self.expressions(expression, flat=True) 3215 3216 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3217 case = exp.case() 3218 for arg in expression.expressions: 3219 case = case.when(arg.is_(exp.null()), exp.null()) 3220 this = self.sql(case.else_(f"({this})")) 3221 3222 this = f" {this}" if this else "" 3223 3224 on = self.sql(expression, "on") 3225 on = f" ON {on}" if on else "" 3226 return f"DISTINCT{this}{on}" 3227 3228 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3229 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3230 3231 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3232 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3233 3234 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3235 this_sql = self.sql(expression, "this") 3236 expression_sql = self.sql(expression, "expression") 3237 kind = "MAX" if expression.args.get("max") else "MIN" 3238 return f"{this_sql} HAVING {kind} {expression_sql}" 3239 3240 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3241 return self.sql( 3242 exp.Cast( 3243 this=exp.Div(this=expression.this, expression=expression.expression), 3244 to=exp.DataType(this=exp.DataType.Type.INT), 3245 ) 3246 ) 3247 3248 def dpipe_sql(self, expression: exp.DPipe) -> str: 3249 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3250 return self.func( 3251 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3252 ) 3253 return self.binary(expression, "||") 3254 3255 def div_sql(self, expression: exp.Div) -> str: 3256 l, r = expression.left, expression.right 3257 3258 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3259 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3260 3261 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3262 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3263 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3264 3265 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3266 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3267 return self.sql( 3268 exp.cast( 3269 l / r, 3270 to=exp.DataType.Type.BIGINT, 3271 ) 3272 ) 3273 3274 return self.binary(expression, "/") 3275 3276 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3277 return self.binary(expression, "OVERLAPS") 3278 3279 def distance_sql(self, expression: exp.Distance) -> str: 3280 return self.binary(expression, "<->") 3281 3282 def dot_sql(self, expression: exp.Dot) -> str: 3283 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3284 3285 def eq_sql(self, expression: exp.EQ) -> str: 3286 return self.binary(expression, "=") 3287 3288 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3289 return self.binary(expression, ":=") 3290 3291 def escape_sql(self, expression: exp.Escape) -> str: 3292 return self.binary(expression, "ESCAPE") 3293 3294 def glob_sql(self, expression: exp.Glob) -> str: 3295 return self.binary(expression, "GLOB") 3296 3297 def gt_sql(self, expression: exp.GT) -> str: 3298 return self.binary(expression, ">") 3299 3300 def gte_sql(self, expression: exp.GTE) -> str: 3301 return self.binary(expression, ">=") 3302 3303 def ilike_sql(self, expression: exp.ILike) -> str: 3304 return self.binary(expression, "ILIKE") 3305 3306 def ilikeany_sql(self, expression: exp.ILikeAny) -> str: 3307 return self.binary(expression, "ILIKE ANY") 3308 3309 def is_sql(self, expression: exp.Is) -> str: 3310 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3311 return self.sql( 3312 expression.this if expression.expression.this else exp.not_(expression.this) 3313 ) 3314 return self.binary(expression, "IS") 3315 3316 def like_sql(self, expression: exp.Like) -> str: 3317 return self.binary(expression, "LIKE") 3318 3319 def likeany_sql(self, expression: exp.LikeAny) -> str: 3320 return self.binary(expression, "LIKE ANY") 3321 3322 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3323 return self.binary(expression, "SIMILAR TO") 3324 3325 def lt_sql(self, expression: exp.LT) -> str: 3326 return self.binary(expression, "<") 3327 3328 def lte_sql(self, expression: exp.LTE) -> str: 3329 return self.binary(expression, "<=") 3330 3331 def mod_sql(self, expression: exp.Mod) -> str: 3332 return self.binary(expression, "%") 3333 3334 def mul_sql(self, expression: exp.Mul) -> str: 3335 return self.binary(expression, "*") 3336 3337 def neq_sql(self, expression: exp.NEQ) -> str: 3338 return self.binary(expression, "<>") 3339 3340 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3341 return self.binary(expression, "IS NOT DISTINCT FROM") 3342 3343 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3344 return self.binary(expression, "IS DISTINCT FROM") 3345 3346 def slice_sql(self, expression: exp.Slice) -> str: 3347 return self.binary(expression, ":") 3348 3349 def sub_sql(self, expression: exp.Sub) -> str: 3350 return self.binary(expression, "-") 3351 3352 def trycast_sql(self, expression: exp.TryCast) -> str: 3353 return self.cast_sql(expression, safe_prefix="TRY_") 3354 3355 def try_sql(self, expression: exp.Try) -> str: 3356 if not self.TRY_SUPPORTED: 3357 self.unsupported("Unsupported TRY function") 3358 return self.sql(expression, "this") 3359 3360 return self.func("TRY", expression.this) 3361 3362 def log_sql(self, expression: exp.Log) -> str: 3363 this = expression.this 3364 expr = expression.expression 3365 3366 if self.dialect.LOG_BASE_FIRST is False: 3367 this, expr = expr, this 3368 elif self.dialect.LOG_BASE_FIRST is None and expr: 3369 if this.name in ("2", "10"): 3370 return self.func(f"LOG{this.name}", expr) 3371 3372 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3373 3374 return self.func("LOG", this, expr) 3375 3376 def use_sql(self, expression: exp.Use) -> str: 3377 kind = self.sql(expression, "kind") 3378 kind = f" {kind}" if kind else "" 3379 this = self.sql(expression, "this") 3380 this = f" {this}" if this else "" 3381 return f"USE{kind}{this}" 3382 3383 def binary(self, expression: exp.Binary, op: str) -> str: 3384 sqls: t.List[str] = [] 3385 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3386 binary_type = type(expression) 3387 3388 while stack: 3389 node = stack.pop() 3390 3391 if type(node) is binary_type: 3392 op_func = node.args.get("operator") 3393 if op_func: 3394 op = f"OPERATOR({self.sql(op_func)})" 3395 3396 stack.append(node.right) 3397 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3398 stack.append(node.left) 3399 else: 3400 sqls.append(self.sql(node)) 3401 3402 return "".join(sqls) 3403 3404 def function_fallback_sql(self, expression: exp.Func) -> str: 3405 args = [] 3406 3407 for key in expression.arg_types: 3408 arg_value = expression.args.get(key) 3409 3410 if isinstance(arg_value, list): 3411 for value in arg_value: 3412 args.append(value) 3413 elif arg_value is not None: 3414 args.append(arg_value) 3415 3416 if self.normalize_functions: 3417 name = expression.sql_name() 3418 else: 3419 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3420 3421 return self.func(name, *args) 3422 3423 def func( 3424 self, 3425 name: str, 3426 *args: t.Optional[exp.Expression | str], 3427 prefix: str = "(", 3428 suffix: str = ")", 3429 normalize: bool = True, 3430 ) -> str: 3431 name = self.normalize_func(name) if normalize else name 3432 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3433 3434 def format_args(self, *args: t.Optional[str | exp.Expression]) -> str: 3435 arg_sqls = tuple( 3436 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3437 ) 3438 if self.pretty and self.too_wide(arg_sqls): 3439 return self.indent("\n" + ",\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True) 3440 return ", ".join(arg_sqls) 3441 3442 def too_wide(self, args: t.Iterable) -> bool: 3443 return sum(len(arg) for arg in args) > self.max_text_width 3444 3445 def format_time( 3446 self, 3447 expression: exp.Expression, 3448 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3449 inverse_time_trie: t.Optional[t.Dict] = None, 3450 ) -> t.Optional[str]: 3451 return format_time( 3452 self.sql(expression, "format"), 3453 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3454 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3455 ) 3456 3457 def expressions( 3458 self, 3459 expression: t.Optional[exp.Expression] = None, 3460 key: t.Optional[str] = None, 3461 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3462 flat: bool = False, 3463 indent: bool = True, 3464 skip_first: bool = False, 3465 skip_last: bool = False, 3466 sep: str = ", ", 3467 prefix: str = "", 3468 dynamic: bool = False, 3469 new_line: bool = False, 3470 ) -> str: 3471 expressions = expression.args.get(key or "expressions") if expression else sqls 3472 3473 if not expressions: 3474 return "" 3475 3476 if flat: 3477 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3478 3479 num_sqls = len(expressions) 3480 result_sqls = [] 3481 3482 for i, e in enumerate(expressions): 3483 sql = self.sql(e, comment=False) 3484 if not sql: 3485 continue 3486 3487 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3488 3489 if self.pretty: 3490 if self.leading_comma: 3491 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3492 else: 3493 result_sqls.append( 3494 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3495 ) 3496 else: 3497 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3498 3499 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3500 if new_line: 3501 result_sqls.insert(0, "") 3502 result_sqls.append("") 3503 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3504 else: 3505 result_sql = "".join(result_sqls) 3506 3507 return ( 3508 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3509 if indent 3510 else result_sql 3511 ) 3512 3513 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3514 flat = flat or isinstance(expression.parent, exp.Properties) 3515 expressions_sql = self.expressions(expression, flat=flat) 3516 if flat: 3517 return f"{op} {expressions_sql}" 3518 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3519 3520 def naked_property(self, expression: exp.Property) -> str: 3521 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3522 if not property_name: 3523 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3524 return f"{property_name} {self.sql(expression, 'this')}" 3525 3526 def tag_sql(self, expression: exp.Tag) -> str: 3527 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3528 3529 def token_sql(self, token_type: TokenType) -> str: 3530 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3531 3532 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3533 this = self.sql(expression, "this") 3534 expressions = self.no_identify(self.expressions, expression) 3535 expressions = ( 3536 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3537 ) 3538 return f"{this}{expressions}" 3539 3540 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3541 this = self.sql(expression, "this") 3542 expressions = self.expressions(expression, flat=True) 3543 return f"{this}({expressions})" 3544 3545 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3546 return self.binary(expression, "=>") 3547 3548 def when_sql(self, expression: exp.When) -> str: 3549 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3550 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3551 condition = self.sql(expression, "condition") 3552 condition = f" AND {condition}" if condition else "" 3553 3554 then_expression = expression.args.get("then") 3555 if isinstance(then_expression, exp.Insert): 3556 this = self.sql(then_expression, "this") 3557 this = f"INSERT {this}" if this else "INSERT" 3558 then = self.sql(then_expression, "expression") 3559 then = f"{this} VALUES {then}" if then else this 3560 elif isinstance(then_expression, exp.Update): 3561 if isinstance(then_expression.args.get("expressions"), exp.Star): 3562 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3563 else: 3564 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}" 3565 else: 3566 then = self.sql(then_expression) 3567 return f"WHEN {matched}{source}{condition} THEN {then}" 3568 3569 def merge_sql(self, expression: exp.Merge) -> str: 3570 table = expression.this 3571 table_alias = "" 3572 3573 hints = table.args.get("hints") 3574 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3575 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3576 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3577 3578 this = self.sql(table) 3579 using = f"USING {self.sql(expression, 'using')}" 3580 on = f"ON {self.sql(expression, 'on')}" 3581 expressions = self.expressions(expression, sep=" ", indent=False) 3582 sep = self.sep() 3583 3584 return self.prepend_ctes( 3585 expression, f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{expressions}" 3586 ) 3587 3588 def tochar_sql(self, expression: exp.ToChar) -> str: 3589 if expression.args.get("format"): 3590 self.unsupported("Format argument unsupported for TO_CHAR/TO_VARCHAR function") 3591 3592 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3593 3594 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3595 if not self.SUPPORTS_TO_NUMBER: 3596 self.unsupported("Unsupported TO_NUMBER function") 3597 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3598 3599 fmt = expression.args.get("format") 3600 if not fmt: 3601 self.unsupported("Conversion format is required for TO_NUMBER") 3602 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3603 3604 return self.func("TO_NUMBER", expression.this, fmt) 3605 3606 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3607 this = self.sql(expression, "this") 3608 kind = self.sql(expression, "kind") 3609 settings_sql = self.expressions(expression, key="settings", sep=" ") 3610 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3611 return f"{this}({kind}{args})" 3612 3613 def dictrange_sql(self, expression: exp.DictRange) -> str: 3614 this = self.sql(expression, "this") 3615 max = self.sql(expression, "max") 3616 min = self.sql(expression, "min") 3617 return f"{this}(MIN {min} MAX {max})" 3618 3619 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 3620 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 3621 3622 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 3623 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 3624 3625 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 3626 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3627 expressions = self.expressions(expression, flat=True) 3628 expressions = f" {self.wrap(expressions)}" if expressions else "" 3629 buckets = self.sql(expression, "buckets") 3630 kind = self.sql(expression, "kind") 3631 buckets = f" BUCKETS {buckets}" if buckets else "" 3632 order = self.sql(expression, "order") 3633 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 3634 3635 def oncluster_sql(self, expression: exp.OnCluster) -> str: 3636 return "" 3637 3638 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 3639 expressions = self.expressions(expression, key="expressions", flat=True) 3640 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 3641 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 3642 buckets = self.sql(expression, "buckets") 3643 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 3644 3645 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 3646 this = self.sql(expression, "this") 3647 having = self.sql(expression, "having") 3648 3649 if having: 3650 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 3651 3652 return self.func("ANY_VALUE", this) 3653 3654 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 3655 transform = self.func("TRANSFORM", *expression.expressions) 3656 row_format_before = self.sql(expression, "row_format_before") 3657 row_format_before = f" {row_format_before}" if row_format_before else "" 3658 record_writer = self.sql(expression, "record_writer") 3659 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 3660 using = f" USING {self.sql(expression, 'command_script')}" 3661 schema = self.sql(expression, "schema") 3662 schema = f" AS {schema}" if schema else "" 3663 row_format_after = self.sql(expression, "row_format_after") 3664 row_format_after = f" {row_format_after}" if row_format_after else "" 3665 record_reader = self.sql(expression, "record_reader") 3666 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 3667 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 3668 3669 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 3670 key_block_size = self.sql(expression, "key_block_size") 3671 if key_block_size: 3672 return f"KEY_BLOCK_SIZE = {key_block_size}" 3673 3674 using = self.sql(expression, "using") 3675 if using: 3676 return f"USING {using}" 3677 3678 parser = self.sql(expression, "parser") 3679 if parser: 3680 return f"WITH PARSER {parser}" 3681 3682 comment = self.sql(expression, "comment") 3683 if comment: 3684 return f"COMMENT {comment}" 3685 3686 visible = expression.args.get("visible") 3687 if visible is not None: 3688 return "VISIBLE" if visible else "INVISIBLE" 3689 3690 engine_attr = self.sql(expression, "engine_attr") 3691 if engine_attr: 3692 return f"ENGINE_ATTRIBUTE = {engine_attr}" 3693 3694 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 3695 if secondary_engine_attr: 3696 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 3697 3698 self.unsupported("Unsupported index constraint option.") 3699 return "" 3700 3701 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 3702 enforced = " ENFORCED" if expression.args.get("enforced") else "" 3703 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 3704 3705 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 3706 kind = self.sql(expression, "kind") 3707 kind = f"{kind} INDEX" if kind else "INDEX" 3708 this = self.sql(expression, "this") 3709 this = f" {this}" if this else "" 3710 index_type = self.sql(expression, "index_type") 3711 index_type = f" USING {index_type}" if index_type else "" 3712 expressions = self.expressions(expression, flat=True) 3713 expressions = f" ({expressions})" if expressions else "" 3714 options = self.expressions(expression, key="options", sep=" ") 3715 options = f" {options}" if options else "" 3716 return f"{kind}{this}{index_type}{expressions}{options}" 3717 3718 def nvl2_sql(self, expression: exp.Nvl2) -> str: 3719 if self.NVL2_SUPPORTED: 3720 return self.function_fallback_sql(expression) 3721 3722 case = exp.Case().when( 3723 expression.this.is_(exp.null()).not_(copy=False), 3724 expression.args["true"], 3725 copy=False, 3726 ) 3727 else_cond = expression.args.get("false") 3728 if else_cond: 3729 case.else_(else_cond, copy=False) 3730 3731 return self.sql(case) 3732 3733 def comprehension_sql(self, expression: exp.Comprehension) -> str: 3734 this = self.sql(expression, "this") 3735 expr = self.sql(expression, "expression") 3736 iterator = self.sql(expression, "iterator") 3737 condition = self.sql(expression, "condition") 3738 condition = f" IF {condition}" if condition else "" 3739 return f"{this} FOR {expr} IN {iterator}{condition}" 3740 3741 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 3742 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 3743 3744 def opclass_sql(self, expression: exp.Opclass) -> str: 3745 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 3746 3747 def predict_sql(self, expression: exp.Predict) -> str: 3748 model = self.sql(expression, "this") 3749 model = f"MODEL {model}" 3750 table = self.sql(expression, "expression") 3751 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 3752 parameters = self.sql(expression, "params_struct") 3753 return self.func("PREDICT", model, table, parameters or None) 3754 3755 def forin_sql(self, expression: exp.ForIn) -> str: 3756 this = self.sql(expression, "this") 3757 expression_sql = self.sql(expression, "expression") 3758 return f"FOR {this} DO {expression_sql}" 3759 3760 def refresh_sql(self, expression: exp.Refresh) -> str: 3761 this = self.sql(expression, "this") 3762 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 3763 return f"REFRESH {table}{this}" 3764 3765 def toarray_sql(self, expression: exp.ToArray) -> str: 3766 arg = expression.this 3767 if not arg.type: 3768 from sqlglot.optimizer.annotate_types import annotate_types 3769 3770 arg = annotate_types(arg) 3771 3772 if arg.is_type(exp.DataType.Type.ARRAY): 3773 return self.sql(arg) 3774 3775 cond_for_null = arg.is_(exp.null()) 3776 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 3777 3778 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 3779 this = expression.this 3780 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 3781 return self.sql(this) 3782 3783 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 3784 3785 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 3786 this = expression.this 3787 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 3788 return self.sql(this) 3789 3790 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP)) 3791 3792 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 3793 this = expression.this 3794 time_format = self.format_time(expression) 3795 3796 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 3797 return self.sql( 3798 exp.cast( 3799 exp.StrToTime(this=this, format=expression.args["format"]), 3800 exp.DataType.Type.DATE, 3801 ) 3802 ) 3803 3804 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 3805 return self.sql(this) 3806 3807 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 3808 3809 def unixdate_sql(self, expression: exp.UnixDate) -> str: 3810 return self.sql( 3811 exp.func( 3812 "DATEDIFF", 3813 expression.this, 3814 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 3815 "day", 3816 ) 3817 ) 3818 3819 def lastday_sql(self, expression: exp.LastDay) -> str: 3820 if self.LAST_DAY_SUPPORTS_DATE_PART: 3821 return self.function_fallback_sql(expression) 3822 3823 unit = expression.text("unit") 3824 if unit and unit != "MONTH": 3825 self.unsupported("Date parts are not supported in LAST_DAY.") 3826 3827 return self.func("LAST_DAY", expression.this) 3828 3829 def dateadd_sql(self, expression: exp.DateAdd) -> str: 3830 from sqlglot.dialects.dialect import unit_to_str 3831 3832 return self.func( 3833 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 3834 ) 3835 3836 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 3837 if self.CAN_IMPLEMENT_ARRAY_ANY: 3838 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 3839 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 3840 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 3841 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 3842 3843 from sqlglot.dialects import Dialect 3844 3845 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 3846 if self.dialect.__class__ != Dialect: 3847 self.unsupported("ARRAY_ANY is unsupported") 3848 3849 return self.function_fallback_sql(expression) 3850 3851 def struct_sql(self, expression: exp.Struct) -> str: 3852 expression.set( 3853 "expressions", 3854 [ 3855 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 3856 if isinstance(e, exp.PropertyEQ) 3857 else e 3858 for e in expression.expressions 3859 ], 3860 ) 3861 3862 return self.function_fallback_sql(expression) 3863 3864 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 3865 low = self.sql(expression, "this") 3866 high = self.sql(expression, "expression") 3867 3868 return f"{low} TO {high}" 3869 3870 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 3871 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 3872 tables = f" {self.expressions(expression)}" 3873 3874 exists = " IF EXISTS" if expression.args.get("exists") else "" 3875 3876 on_cluster = self.sql(expression, "cluster") 3877 on_cluster = f" {on_cluster}" if on_cluster else "" 3878 3879 identity = self.sql(expression, "identity") 3880 identity = f" {identity} IDENTITY" if identity else "" 3881 3882 option = self.sql(expression, "option") 3883 option = f" {option}" if option else "" 3884 3885 partition = self.sql(expression, "partition") 3886 partition = f" {partition}" if partition else "" 3887 3888 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 3889 3890 # This transpiles T-SQL's CONVERT function 3891 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 3892 def convert_sql(self, expression: exp.Convert) -> str: 3893 to = expression.this 3894 value = expression.expression 3895 style = expression.args.get("style") 3896 safe = expression.args.get("safe") 3897 strict = expression.args.get("strict") 3898 3899 if not to or not value: 3900 return "" 3901 3902 # Retrieve length of datatype and override to default if not specified 3903 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 3904 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 3905 3906 transformed: t.Optional[exp.Expression] = None 3907 cast = exp.Cast if strict else exp.TryCast 3908 3909 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 3910 if isinstance(style, exp.Literal) and style.is_int: 3911 from sqlglot.dialects.tsql import TSQL 3912 3913 style_value = style.name 3914 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 3915 if not converted_style: 3916 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 3917 3918 fmt = exp.Literal.string(converted_style) 3919 3920 if to.this == exp.DataType.Type.DATE: 3921 transformed = exp.StrToDate(this=value, format=fmt) 3922 elif to.this == exp.DataType.Type.DATETIME: 3923 transformed = exp.StrToTime(this=value, format=fmt) 3924 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 3925 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 3926 elif to.this == exp.DataType.Type.TEXT: 3927 transformed = exp.TimeToStr(this=value, format=fmt) 3928 3929 if not transformed: 3930 transformed = cast(this=value, to=to, safe=safe) 3931 3932 return self.sql(transformed) 3933 3934 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 3935 this = expression.this 3936 if isinstance(this, exp.JSONPathWildcard): 3937 this = self.json_path_part(this) 3938 return f".{this}" if this else "" 3939 3940 if exp.SAFE_IDENTIFIER_RE.match(this): 3941 return f".{this}" 3942 3943 this = self.json_path_part(this) 3944 return f"[{this}]" if self.JSON_PATH_BRACKETED_KEY_SUPPORTED else f".{this}" 3945 3946 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 3947 this = self.json_path_part(expression.this) 3948 return f"[{this}]" if this else "" 3949 3950 def _simplify_unless_literal(self, expression: E) -> E: 3951 if not isinstance(expression, exp.Literal): 3952 from sqlglot.optimizer.simplify import simplify 3953 3954 expression = simplify(expression, dialect=self.dialect) 3955 3956 return expression 3957 3958 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 3959 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 3960 # The first modifier here will be the one closest to the AggFunc's arg 3961 mods = sorted( 3962 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 3963 key=lambda x: 0 3964 if isinstance(x, exp.HavingMax) 3965 else (1 if isinstance(x, exp.Order) else 2), 3966 ) 3967 3968 if mods: 3969 mod = mods[0] 3970 this = expression.__class__(this=mod.this.copy()) 3971 this.meta["inline"] = True 3972 mod.this.replace(this) 3973 return self.sql(expression.this) 3974 3975 agg_func = expression.find(exp.AggFunc) 3976 3977 if agg_func: 3978 return self.sql(agg_func)[:-1] + f" {text})" 3979 3980 return f"{self.sql(expression, 'this')} {text}" 3981 3982 def _replace_line_breaks(self, string: str) -> str: 3983 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 3984 if self.pretty: 3985 return string.replace("\n", self.SENTINEL_LINE_BREAK) 3986 return string 3987 3988 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 3989 option = self.sql(expression, "this") 3990 3991 if expression.expressions: 3992 upper = option.upper() 3993 3994 # Snowflake FILE_FORMAT options are separated by whitespace 3995 sep = " " if upper == "FILE_FORMAT" else ", " 3996 3997 # Databricks copy/format options do not set their list of values with EQ 3998 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 3999 values = self.expressions(expression, flat=True, sep=sep) 4000 return f"{option}{op}({values})" 4001 4002 value = self.sql(expression, "expression") 4003 4004 if not value: 4005 return option 4006 4007 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4008 4009 return f"{option}{op}{value}" 4010 4011 def credentials_sql(self, expression: exp.Credentials) -> str: 4012 cred_expr = expression.args.get("credentials") 4013 if isinstance(cred_expr, exp.Literal): 4014 # Redshift case: CREDENTIALS <string> 4015 credentials = self.sql(expression, "credentials") 4016 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4017 else: 4018 # Snowflake case: CREDENTIALS = (...) 4019 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4020 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4021 4022 storage = self.sql(expression, "storage") 4023 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4024 4025 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4026 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4027 4028 iam_role = self.sql(expression, "iam_role") 4029 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4030 4031 region = self.sql(expression, "region") 4032 region = f" REGION {region}" if region else "" 4033 4034 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4035 4036 def copy_sql(self, expression: exp.Copy) -> str: 4037 this = self.sql(expression, "this") 4038 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4039 4040 credentials = self.sql(expression, "credentials") 4041 credentials = self.seg(credentials) if credentials else "" 4042 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4043 files = self.expressions(expression, key="files", flat=True) 4044 4045 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4046 params = self.expressions( 4047 expression, 4048 key="params", 4049 sep=sep, 4050 new_line=True, 4051 skip_last=True, 4052 skip_first=True, 4053 indent=self.COPY_PARAMS_ARE_WRAPPED, 4054 ) 4055 4056 if params: 4057 if self.COPY_PARAMS_ARE_WRAPPED: 4058 params = f" WITH ({params})" 4059 elif not self.pretty: 4060 params = f" {params}" 4061 4062 return f"COPY{this}{kind} {files}{credentials}{params}" 4063 4064 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4065 return "" 4066 4067 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4068 on_sql = "ON" if expression.args.get("on") else "OFF" 4069 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4070 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4071 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4072 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4073 4074 if filter_col or retention_period: 4075 on_sql = self.func("ON", filter_col, retention_period) 4076 4077 return f"DATA_DELETION={on_sql}" 4078 4079 def maskingpolicycolumnconstraint_sql( 4080 self, expression: exp.MaskingPolicyColumnConstraint 4081 ) -> str: 4082 this = self.sql(expression, "this") 4083 expressions = self.expressions(expression, flat=True) 4084 expressions = f" USING ({expressions})" if expressions else "" 4085 return f"MASKING POLICY {this}{expressions}" 4086 4087 def gapfill_sql(self, expression: exp.GapFill) -> str: 4088 this = self.sql(expression, "this") 4089 this = f"TABLE {this}" 4090 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4091 4092 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4093 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4094 4095 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4096 this = self.sql(expression, "this") 4097 expr = expression.expression 4098 4099 if isinstance(expr, exp.Func): 4100 # T-SQL's CLR functions are case sensitive 4101 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4102 else: 4103 expr = self.sql(expression, "expression") 4104 4105 return self.scope_resolution(expr, this) 4106 4107 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4108 if self.PARSE_JSON_NAME is None: 4109 return self.sql(expression.this) 4110 4111 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4112 4113 def rand_sql(self, expression: exp.Rand) -> str: 4114 lower = self.sql(expression, "lower") 4115 upper = self.sql(expression, "upper") 4116 4117 if lower and upper: 4118 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4119 return self.func("RAND", expression.this) 4120 4121 def changes_sql(self, expression: exp.Changes) -> str: 4122 information = self.sql(expression, "information") 4123 information = f"INFORMATION => {information}" 4124 at_before = self.sql(expression, "at_before") 4125 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4126 end = self.sql(expression, "end") 4127 end = f"{self.seg('')}{end}" if end else "" 4128 4129 return f"CHANGES ({information}){at_before}{end}" 4130 4131 def pad_sql(self, expression: exp.Pad) -> str: 4132 prefix = "L" if expression.args.get("is_left") else "R" 4133 4134 fill_pattern = self.sql(expression, "fill_pattern") or None 4135 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4136 fill_pattern = "' '" 4137 4138 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4139 4140 def summarize_sql(self, expression: exp.Summarize) -> str: 4141 table = " TABLE" if expression.args.get("table") else "" 4142 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4143 4144 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4145 generate_series = exp.GenerateSeries(**expression.args) 4146 4147 parent = expression.parent 4148 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4149 parent = parent.parent 4150 4151 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4152 return self.sql(exp.Unnest(expressions=[generate_series])) 4153 4154 if isinstance(parent, exp.Select): 4155 self.unsupported("GenerateSeries projection unnesting is not supported.") 4156 4157 return self.sql(generate_series) 4158 4159 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4160 exprs = expression.expressions 4161 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4162 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4163 else: 4164 rhs = self.expressions(expression) 4165 4166 return self.func(name, expression.this, rhs) 4167 4168 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4169 if self.SUPPORTS_CONVERT_TIMEZONE: 4170 return self.function_fallback_sql(expression) 4171 4172 source_tz = expression.args.get("source_tz") 4173 target_tz = expression.args.get("target_tz") 4174 timestamp = expression.args.get("timestamp") 4175 4176 if source_tz and timestamp: 4177 timestamp = exp.AtTimeZone( 4178 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4179 ) 4180 4181 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4182 4183 return self.sql(expr) 4184 4185 def json_sql(self, expression: exp.JSON) -> str: 4186 this = self.sql(expression, "this") 4187 this = f" {this}" if this else "" 4188 4189 _with = expression.args.get("with") 4190 4191 if _with is None: 4192 with_sql = "" 4193 elif not _with: 4194 with_sql = " WITHOUT" 4195 else: 4196 with_sql = " WITH" 4197 4198 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4199 4200 return f"JSON{this}{with_sql}{unique_sql}" 4201 4202 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4203 def _generate_on_options(arg: t.Any) -> str: 4204 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4205 4206 path = self.sql(expression, "path") 4207 returning = self.sql(expression, "returning") 4208 returning = f" RETURNING {returning}" if returning else "" 4209 4210 on_condition = self.sql(expression, "on_condition") 4211 on_condition = f" {on_condition}" if on_condition else "" 4212 4213 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4214 4215 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4216 else_ = "ELSE " if expression.args.get("else_") else "" 4217 condition = self.sql(expression, "expression") 4218 condition = f"WHEN {condition} THEN " if condition else else_ 4219 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4220 return f"{condition}{insert}" 4221 4222 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4223 kind = self.sql(expression, "kind") 4224 expressions = self.seg(self.expressions(expression, sep=" ")) 4225 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4226 return res 4227 4228 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4229 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4230 empty = expression.args.get("empty") 4231 empty = ( 4232 f"DEFAULT {empty} ON EMPTY" 4233 if isinstance(empty, exp.Expression) 4234 else self.sql(expression, "empty") 4235 ) 4236 4237 error = expression.args.get("error") 4238 error = ( 4239 f"DEFAULT {error} ON ERROR" 4240 if isinstance(error, exp.Expression) 4241 else self.sql(expression, "error") 4242 ) 4243 4244 if error and empty: 4245 error = ( 4246 f"{empty} {error}" 4247 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4248 else f"{error} {empty}" 4249 ) 4250 empty = "" 4251 4252 null = self.sql(expression, "null") 4253 4254 return f"{empty}{error}{null}" 4255 4256 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4257 this = self.sql(expression, "this") 4258 path = self.sql(expression, "path") 4259 4260 passing = self.expressions(expression, "passing") 4261 passing = f" PASSING {passing}" if passing else "" 4262 4263 on_condition = self.sql(expression, "on_condition") 4264 on_condition = f" {on_condition}" if on_condition else "" 4265 4266 path = f"{path}{passing}{on_condition}" 4267 4268 return self.func("JSON_EXISTS", this, path) 4269 4270 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4271 array_agg = self.function_fallback_sql(expression) 4272 4273 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4274 parent = expression.parent 4275 if isinstance(parent, exp.Filter): 4276 parent_cond = parent.expression.this 4277 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4278 else: 4279 array_agg = f"{array_agg} FILTER(WHERE {self.sql(expression, 'this')} IS NOT NULL)" 4280 4281 return array_agg
logger =
<Logger sqlglot (WARNING)>
ESCAPED_UNICODE_RE =
re.compile('\\\\(\\d+)')
class
Generator:
37class Generator(metaclass=_Generator): 38 """ 39 Generator converts a given syntax tree to the corresponding SQL string. 40 41 Args: 42 pretty: Whether to format the produced SQL string. 43 Default: False. 44 identify: Determines when an identifier should be quoted. Possible values are: 45 False (default): Never quote, except in cases where it's mandatory by the dialect. 46 True or 'always': Always quote. 47 'safe': Only quote identifiers that are case insensitive. 48 normalize: Whether to normalize identifiers to lowercase. 49 Default: False. 50 pad: The pad size in a formatted string. For example, this affects the indentation of 51 a projection in a query, relative to its nesting level. 52 Default: 2. 53 indent: The indentation size in a formatted string. For example, this affects the 54 indentation of subqueries and filters under a `WHERE` clause. 55 Default: 2. 56 normalize_functions: How to normalize function names. Possible values are: 57 "upper" or True (default): Convert names to uppercase. 58 "lower": Convert names to lowercase. 59 False: Disables function name normalization. 60 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 61 Default ErrorLevel.WARN. 62 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 63 This is only relevant if unsupported_level is ErrorLevel.RAISE. 64 Default: 3 65 leading_comma: Whether the comma is leading or trailing in select expressions. 66 This is only relevant when generating in pretty mode. 67 Default: False 68 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 69 The default is on the smaller end because the length only represents a segment and not the true 70 line length. 71 Default: 80 72 comments: Whether to preserve comments in the output SQL code. 73 Default: True 74 """ 75 76 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 77 **JSON_PATH_PART_TRANSFORMS, 78 exp.AllowedValuesProperty: lambda self, 79 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 80 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 81 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 82 exp.CaseSpecificColumnConstraint: lambda _, 83 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 84 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 85 exp.CharacterSetProperty: lambda self, 86 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 87 exp.ClusteredColumnConstraint: lambda self, 88 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 89 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 90 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 91 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 92 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 93 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 94 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 95 exp.DynamicProperty: lambda *_: "DYNAMIC", 96 exp.EmptyProperty: lambda *_: "EMPTY", 97 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 98 exp.EphemeralColumnConstraint: lambda self, 99 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 100 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 101 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 102 exp.Except: lambda self, e: self.set_operations(e), 103 exp.ExternalProperty: lambda *_: "EXTERNAL", 104 exp.GlobalProperty: lambda *_: "GLOBAL", 105 exp.HeapProperty: lambda *_: "HEAP", 106 exp.IcebergProperty: lambda *_: "ICEBERG", 107 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 108 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 109 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 110 exp.Intersect: lambda self, e: self.set_operations(e), 111 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 112 exp.LanguageProperty: lambda self, e: self.naked_property(e), 113 exp.LocationProperty: lambda self, e: self.naked_property(e), 114 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 115 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 116 exp.NonClusteredColumnConstraint: lambda self, 117 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 118 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 119 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 120 exp.OnCommitProperty: lambda _, 121 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 122 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 123 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 124 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 125 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 126 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 127 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 128 exp.ProjectionPolicyColumnConstraint: lambda self, 129 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 130 exp.RemoteWithConnectionModelProperty: lambda self, 131 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 132 exp.ReturnsProperty: lambda self, e: ( 133 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 134 ), 135 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 136 exp.SecureProperty: lambda *_: "SECURE", 137 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 138 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 139 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 140 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 141 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 142 exp.SqlReadWriteProperty: lambda _, e: e.name, 143 exp.SqlSecurityProperty: lambda _, 144 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 145 exp.StabilityProperty: lambda _, e: e.name, 146 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 147 exp.StreamingTableProperty: lambda *_: "STREAMING", 148 exp.StrictProperty: lambda *_: "STRICT", 149 exp.TemporaryProperty: lambda *_: "TEMPORARY", 150 exp.TagColumnConstraint: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 151 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 152 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 153 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 154 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 155 exp.TransientProperty: lambda *_: "TRANSIENT", 156 exp.Union: lambda self, e: self.set_operations(e), 157 exp.UnloggedProperty: lambda *_: "UNLOGGED", 158 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 159 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 160 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 161 exp.VolatileProperty: lambda *_: "VOLATILE", 162 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 163 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 164 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 165 } 166 167 # Whether null ordering is supported in order by 168 # True: Full Support, None: No support, False: No support in window specifications 169 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 170 171 # Whether ignore nulls is inside the agg or outside. 172 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 173 IGNORE_NULLS_IN_FUNC = False 174 175 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 176 LOCKING_READS_SUPPORTED = False 177 178 # Whether the EXCEPT and INTERSECT operations can return duplicates 179 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 180 181 # Wrap derived values in parens, usually standard but spark doesn't support it 182 WRAP_DERIVED_VALUES = True 183 184 # Whether create function uses an AS before the RETURN 185 CREATE_FUNCTION_RETURN_AS = True 186 187 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 188 MATCHED_BY_SOURCE = True 189 190 # Whether the INTERVAL expression works only with values like '1 day' 191 SINGLE_STRING_INTERVAL = False 192 193 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 194 INTERVAL_ALLOWS_PLURAL_FORM = True 195 196 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 197 LIMIT_FETCH = "ALL" 198 199 # Whether limit and fetch allows expresions or just limits 200 LIMIT_ONLY_LITERALS = False 201 202 # Whether a table is allowed to be renamed with a db 203 RENAME_TABLE_WITH_DB = True 204 205 # The separator for grouping sets and rollups 206 GROUPINGS_SEP = "," 207 208 # The string used for creating an index on a table 209 INDEX_ON = "ON" 210 211 # Whether join hints should be generated 212 JOIN_HINTS = True 213 214 # Whether table hints should be generated 215 TABLE_HINTS = True 216 217 # Whether query hints should be generated 218 QUERY_HINTS = True 219 220 # What kind of separator to use for query hints 221 QUERY_HINT_SEP = ", " 222 223 # Whether comparing against booleans (e.g. x IS TRUE) is supported 224 IS_BOOL_ALLOWED = True 225 226 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 227 DUPLICATE_KEY_UPDATE_WITH_SET = True 228 229 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 230 LIMIT_IS_TOP = False 231 232 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 233 RETURNING_END = True 234 235 # Whether to generate an unquoted value for EXTRACT's date part argument 236 EXTRACT_ALLOWS_QUOTES = True 237 238 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 239 TZ_TO_WITH_TIME_ZONE = False 240 241 # Whether the NVL2 function is supported 242 NVL2_SUPPORTED = True 243 244 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 245 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 246 247 # Whether VALUES statements can be used as derived tables. 248 # MySQL 5 and Redshift do not allow this, so when False, it will convert 249 # SELECT * VALUES into SELECT UNION 250 VALUES_AS_TABLE = True 251 252 # Whether the word COLUMN is included when adding a column with ALTER TABLE 253 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 254 255 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 256 UNNEST_WITH_ORDINALITY = True 257 258 # Whether FILTER (WHERE cond) can be used for conditional aggregation 259 AGGREGATE_FILTER_SUPPORTED = True 260 261 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 262 SEMI_ANTI_JOIN_WITH_SIDE = True 263 264 # Whether to include the type of a computed column in the CREATE DDL 265 COMPUTED_COLUMN_WITH_TYPE = True 266 267 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 268 SUPPORTS_TABLE_COPY = True 269 270 # Whether parentheses are required around the table sample's expression 271 TABLESAMPLE_REQUIRES_PARENS = True 272 273 # Whether a table sample clause's size needs to be followed by the ROWS keyword 274 TABLESAMPLE_SIZE_IS_ROWS = True 275 276 # The keyword(s) to use when generating a sample clause 277 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 278 279 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 280 TABLESAMPLE_WITH_METHOD = True 281 282 # The keyword to use when specifying the seed of a sample clause 283 TABLESAMPLE_SEED_KEYWORD = "SEED" 284 285 # Whether COLLATE is a function instead of a binary operator 286 COLLATE_IS_FUNC = False 287 288 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 289 DATA_TYPE_SPECIFIERS_ALLOWED = False 290 291 # Whether conditions require booleans WHERE x = 0 vs WHERE x 292 ENSURE_BOOLS = False 293 294 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 295 CTE_RECURSIVE_KEYWORD_REQUIRED = True 296 297 # Whether CONCAT requires >1 arguments 298 SUPPORTS_SINGLE_ARG_CONCAT = True 299 300 # Whether LAST_DAY function supports a date part argument 301 LAST_DAY_SUPPORTS_DATE_PART = True 302 303 # Whether named columns are allowed in table aliases 304 SUPPORTS_TABLE_ALIAS_COLUMNS = True 305 306 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 307 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 308 309 # What delimiter to use for separating JSON key/value pairs 310 JSON_KEY_VALUE_PAIR_SEP = ":" 311 312 # INSERT OVERWRITE TABLE x override 313 INSERT_OVERWRITE = " OVERWRITE TABLE" 314 315 # Whether the SELECT .. INTO syntax is used instead of CTAS 316 SUPPORTS_SELECT_INTO = False 317 318 # Whether UNLOGGED tables can be created 319 SUPPORTS_UNLOGGED_TABLES = False 320 321 # Whether the CREATE TABLE LIKE statement is supported 322 SUPPORTS_CREATE_TABLE_LIKE = True 323 324 # Whether the LikeProperty needs to be specified inside of the schema clause 325 LIKE_PROPERTY_INSIDE_SCHEMA = False 326 327 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 328 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 329 MULTI_ARG_DISTINCT = True 330 331 # Whether the JSON extraction operators expect a value of type JSON 332 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 333 334 # Whether bracketed keys like ["foo"] are supported in JSON paths 335 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 336 337 # Whether to escape keys using single quotes in JSON paths 338 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 339 340 # The JSONPathPart expressions supported by this dialect 341 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 342 343 # Whether any(f(x) for x in array) can be implemented by this dialect 344 CAN_IMPLEMENT_ARRAY_ANY = False 345 346 # Whether the function TO_NUMBER is supported 347 SUPPORTS_TO_NUMBER = True 348 349 # Whether or not set op modifiers apply to the outer set op or select. 350 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 351 # True means limit 1 happens after the set op, False means it it happens on y. 352 SET_OP_MODIFIERS = True 353 354 # Whether parameters from COPY statement are wrapped in parentheses 355 COPY_PARAMS_ARE_WRAPPED = True 356 357 # Whether values of params are set with "=" token or empty space 358 COPY_PARAMS_EQ_REQUIRED = False 359 360 # Whether COPY statement has INTO keyword 361 COPY_HAS_INTO_KEYWORD = True 362 363 # Whether the conditional TRY(expression) function is supported 364 TRY_SUPPORTED = True 365 366 # Whether the UESCAPE syntax in unicode strings is supported 367 SUPPORTS_UESCAPE = True 368 369 # The keyword to use when generating a star projection with excluded columns 370 STAR_EXCEPT = "EXCEPT" 371 372 # The HEX function name 373 HEX_FUNC = "HEX" 374 375 # The keywords to use when prefixing & separating WITH based properties 376 WITH_PROPERTIES_PREFIX = "WITH" 377 378 # Whether to quote the generated expression of exp.JsonPath 379 QUOTE_JSON_PATH = True 380 381 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 382 PAD_FILL_PATTERN_IS_REQUIRED = False 383 384 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 385 SUPPORTS_EXPLODING_PROJECTIONS = True 386 387 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 388 ARRAY_CONCAT_IS_VAR_LEN = True 389 390 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 391 SUPPORTS_CONVERT_TIMEZONE = False 392 393 # Whether nullable types can be constructed, e.g. `Nullable(Int64)` 394 SUPPORTS_NULLABLE_TYPES = True 395 396 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 397 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 398 399 TYPE_MAPPING = { 400 exp.DataType.Type.NCHAR: "CHAR", 401 exp.DataType.Type.NVARCHAR: "VARCHAR", 402 exp.DataType.Type.MEDIUMTEXT: "TEXT", 403 exp.DataType.Type.LONGTEXT: "TEXT", 404 exp.DataType.Type.TINYTEXT: "TEXT", 405 exp.DataType.Type.MEDIUMBLOB: "BLOB", 406 exp.DataType.Type.LONGBLOB: "BLOB", 407 exp.DataType.Type.TINYBLOB: "BLOB", 408 exp.DataType.Type.INET: "INET", 409 exp.DataType.Type.ROWVERSION: "VARBINARY", 410 } 411 412 TIME_PART_SINGULARS = { 413 "MICROSECONDS": "MICROSECOND", 414 "SECONDS": "SECOND", 415 "MINUTES": "MINUTE", 416 "HOURS": "HOUR", 417 "DAYS": "DAY", 418 "WEEKS": "WEEK", 419 "MONTHS": "MONTH", 420 "QUARTERS": "QUARTER", 421 "YEARS": "YEAR", 422 } 423 424 AFTER_HAVING_MODIFIER_TRANSFORMS = { 425 "cluster": lambda self, e: self.sql(e, "cluster"), 426 "distribute": lambda self, e: self.sql(e, "distribute"), 427 "sort": lambda self, e: self.sql(e, "sort"), 428 "windows": lambda self, e: ( 429 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 430 if e.args.get("windows") 431 else "" 432 ), 433 "qualify": lambda self, e: self.sql(e, "qualify"), 434 } 435 436 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 437 438 STRUCT_DELIMITER = ("<", ">") 439 440 PARAMETER_TOKEN = "@" 441 NAMED_PLACEHOLDER_TOKEN = ":" 442 443 PROPERTIES_LOCATION = { 444 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 445 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 446 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 447 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 448 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 449 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 450 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 451 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 452 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 453 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 454 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 455 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 456 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 457 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 458 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 459 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 460 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 461 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 462 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 463 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 464 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 465 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 466 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 467 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 468 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 469 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 470 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 471 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 472 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 473 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 474 exp.HeapProperty: exp.Properties.Location.POST_WITH, 475 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 476 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 477 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 478 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 479 exp.JournalProperty: exp.Properties.Location.POST_NAME, 480 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 481 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 482 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 483 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 484 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 485 exp.LogProperty: exp.Properties.Location.POST_NAME, 486 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 487 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 488 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 489 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 490 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 491 exp.Order: exp.Properties.Location.POST_SCHEMA, 492 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 493 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 494 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 495 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 496 exp.Property: exp.Properties.Location.POST_WITH, 497 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 498 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 499 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 500 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 501 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 502 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 503 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 504 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 505 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 506 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 507 exp.Set: exp.Properties.Location.POST_SCHEMA, 508 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 509 exp.SetProperty: exp.Properties.Location.POST_CREATE, 510 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 511 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 512 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 513 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 514 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 515 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 516 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 517 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 518 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 519 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 520 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 521 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 522 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 523 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 524 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 525 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 526 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 527 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 528 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 529 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 530 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 531 } 532 533 # Keywords that can't be used as unquoted identifier names 534 RESERVED_KEYWORDS: t.Set[str] = set() 535 536 # Expressions whose comments are separated from them for better formatting 537 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 538 exp.Command, 539 exp.Create, 540 exp.Delete, 541 exp.Drop, 542 exp.From, 543 exp.Insert, 544 exp.Join, 545 exp.MultitableInserts, 546 exp.Select, 547 exp.SetOperation, 548 exp.Update, 549 exp.Where, 550 exp.With, 551 ) 552 553 # Expressions that should not have their comments generated in maybe_comment 554 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 555 exp.Binary, 556 exp.SetOperation, 557 ) 558 559 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 560 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 561 exp.Column, 562 exp.Literal, 563 exp.Neg, 564 exp.Paren, 565 ) 566 567 PARAMETERIZABLE_TEXT_TYPES = { 568 exp.DataType.Type.NVARCHAR, 569 exp.DataType.Type.VARCHAR, 570 exp.DataType.Type.CHAR, 571 exp.DataType.Type.NCHAR, 572 } 573 574 # Expressions that need to have all CTEs under them bubbled up to them 575 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 576 577 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 578 579 __slots__ = ( 580 "pretty", 581 "identify", 582 "normalize", 583 "pad", 584 "_indent", 585 "normalize_functions", 586 "unsupported_level", 587 "max_unsupported", 588 "leading_comma", 589 "max_text_width", 590 "comments", 591 "dialect", 592 "unsupported_messages", 593 "_escaped_quote_end", 594 "_escaped_identifier_end", 595 "_next_name", 596 ) 597 598 def __init__( 599 self, 600 pretty: t.Optional[bool] = None, 601 identify: str | bool = False, 602 normalize: bool = False, 603 pad: int = 2, 604 indent: int = 2, 605 normalize_functions: t.Optional[str | bool] = None, 606 unsupported_level: ErrorLevel = ErrorLevel.WARN, 607 max_unsupported: int = 3, 608 leading_comma: bool = False, 609 max_text_width: int = 80, 610 comments: bool = True, 611 dialect: DialectType = None, 612 ): 613 import sqlglot 614 from sqlglot.dialects import Dialect 615 616 self.pretty = pretty if pretty is not None else sqlglot.pretty 617 self.identify = identify 618 self.normalize = normalize 619 self.pad = pad 620 self._indent = indent 621 self.unsupported_level = unsupported_level 622 self.max_unsupported = max_unsupported 623 self.leading_comma = leading_comma 624 self.max_text_width = max_text_width 625 self.comments = comments 626 self.dialect = Dialect.get_or_raise(dialect) 627 628 # This is both a Dialect property and a Generator argument, so we prioritize the latter 629 self.normalize_functions = ( 630 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 631 ) 632 633 self.unsupported_messages: t.List[str] = [] 634 self._escaped_quote_end: str = ( 635 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 636 ) 637 self._escaped_identifier_end: str = ( 638 self.dialect.tokenizer_class.IDENTIFIER_ESCAPES[0] + self.dialect.IDENTIFIER_END 639 ) 640 641 self._next_name = name_sequence("_t") 642 643 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 644 """ 645 Generates the SQL string corresponding to the given syntax tree. 646 647 Args: 648 expression: The syntax tree. 649 copy: Whether to copy the expression. The generator performs mutations so 650 it is safer to copy. 651 652 Returns: 653 The SQL string corresponding to `expression`. 654 """ 655 if copy: 656 expression = expression.copy() 657 658 expression = self.preprocess(expression) 659 660 self.unsupported_messages = [] 661 sql = self.sql(expression).strip() 662 663 if self.pretty: 664 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 665 666 if self.unsupported_level == ErrorLevel.IGNORE: 667 return sql 668 669 if self.unsupported_level == ErrorLevel.WARN: 670 for msg in self.unsupported_messages: 671 logger.warning(msg) 672 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 673 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 674 675 return sql 676 677 def preprocess(self, expression: exp.Expression) -> exp.Expression: 678 """Apply generic preprocessing transformations to a given expression.""" 679 if ( 680 not expression.parent 681 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 682 and any(node.parent is not expression for node in expression.find_all(exp.With)) 683 ): 684 from sqlglot.transforms import move_ctes_to_top_level 685 686 expression = move_ctes_to_top_level(expression) 687 688 if self.ENSURE_BOOLS: 689 from sqlglot.transforms import ensure_bools 690 691 expression = ensure_bools(expression) 692 693 return expression 694 695 def unsupported(self, message: str) -> None: 696 if self.unsupported_level == ErrorLevel.IMMEDIATE: 697 raise UnsupportedError(message) 698 self.unsupported_messages.append(message) 699 700 def sep(self, sep: str = " ") -> str: 701 return f"{sep.strip()}\n" if self.pretty else sep 702 703 def seg(self, sql: str, sep: str = " ") -> str: 704 return f"{self.sep(sep)}{sql}" 705 706 def pad_comment(self, comment: str) -> str: 707 comment = " " + comment if comment[0].strip() else comment 708 comment = comment + " " if comment[-1].strip() else comment 709 return comment 710 711 def maybe_comment( 712 self, 713 sql: str, 714 expression: t.Optional[exp.Expression] = None, 715 comments: t.Optional[t.List[str]] = None, 716 separated: bool = False, 717 ) -> str: 718 comments = ( 719 ((expression and expression.comments) if comments is None else comments) # type: ignore 720 if self.comments 721 else None 722 ) 723 724 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 725 return sql 726 727 comments_sql = " ".join( 728 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 729 ) 730 731 if not comments_sql: 732 return sql 733 734 comments_sql = self._replace_line_breaks(comments_sql) 735 736 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 737 return ( 738 f"{self.sep()}{comments_sql}{sql}" 739 if not sql or sql[0].isspace() 740 else f"{comments_sql}{self.sep()}{sql}" 741 ) 742 743 return f"{sql} {comments_sql}" 744 745 def wrap(self, expression: exp.Expression | str) -> str: 746 this_sql = ( 747 self.sql(expression) 748 if isinstance(expression, exp.UNWRAPPED_QUERIES) 749 else self.sql(expression, "this") 750 ) 751 if not this_sql: 752 return "()" 753 754 this_sql = self.indent(this_sql, level=1, pad=0) 755 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 756 757 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 758 original = self.identify 759 self.identify = False 760 result = func(*args, **kwargs) 761 self.identify = original 762 return result 763 764 def normalize_func(self, name: str) -> str: 765 if self.normalize_functions == "upper" or self.normalize_functions is True: 766 return name.upper() 767 if self.normalize_functions == "lower": 768 return name.lower() 769 return name 770 771 def indent( 772 self, 773 sql: str, 774 level: int = 0, 775 pad: t.Optional[int] = None, 776 skip_first: bool = False, 777 skip_last: bool = False, 778 ) -> str: 779 if not self.pretty or not sql: 780 return sql 781 782 pad = self.pad if pad is None else pad 783 lines = sql.split("\n") 784 785 return "\n".join( 786 ( 787 line 788 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 789 else f"{' ' * (level * self._indent + pad)}{line}" 790 ) 791 for i, line in enumerate(lines) 792 ) 793 794 def sql( 795 self, 796 expression: t.Optional[str | exp.Expression], 797 key: t.Optional[str] = None, 798 comment: bool = True, 799 ) -> str: 800 if not expression: 801 return "" 802 803 if isinstance(expression, str): 804 return expression 805 806 if key: 807 value = expression.args.get(key) 808 if value: 809 return self.sql(value) 810 return "" 811 812 transform = self.TRANSFORMS.get(expression.__class__) 813 814 if callable(transform): 815 sql = transform(self, expression) 816 elif isinstance(expression, exp.Expression): 817 exp_handler_name = f"{expression.key}_sql" 818 819 if hasattr(self, exp_handler_name): 820 sql = getattr(self, exp_handler_name)(expression) 821 elif isinstance(expression, exp.Func): 822 sql = self.function_fallback_sql(expression) 823 elif isinstance(expression, exp.Property): 824 sql = self.property_sql(expression) 825 else: 826 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 827 else: 828 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 829 830 return self.maybe_comment(sql, expression) if self.comments and comment else sql 831 832 def uncache_sql(self, expression: exp.Uncache) -> str: 833 table = self.sql(expression, "this") 834 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 835 return f"UNCACHE TABLE{exists_sql} {table}" 836 837 def cache_sql(self, expression: exp.Cache) -> str: 838 lazy = " LAZY" if expression.args.get("lazy") else "" 839 table = self.sql(expression, "this") 840 options = expression.args.get("options") 841 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 842 sql = self.sql(expression, "expression") 843 sql = f" AS{self.sep()}{sql}" if sql else "" 844 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 845 return self.prepend_ctes(expression, sql) 846 847 def characterset_sql(self, expression: exp.CharacterSet) -> str: 848 if isinstance(expression.parent, exp.Cast): 849 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 850 default = "DEFAULT " if expression.args.get("default") else "" 851 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 852 853 def column_parts(self, expression: exp.Column) -> str: 854 return ".".join( 855 self.sql(part) 856 for part in ( 857 expression.args.get("catalog"), 858 expression.args.get("db"), 859 expression.args.get("table"), 860 expression.args.get("this"), 861 ) 862 if part 863 ) 864 865 def column_sql(self, expression: exp.Column) -> str: 866 join_mark = " (+)" if expression.args.get("join_mark") else "" 867 868 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 869 join_mark = "" 870 self.unsupported("Outer join syntax using the (+) operator is not supported.") 871 872 return f"{self.column_parts(expression)}{join_mark}" 873 874 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 875 this = self.sql(expression, "this") 876 this = f" {this}" if this else "" 877 position = self.sql(expression, "position") 878 return f"{position}{this}" 879 880 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 881 column = self.sql(expression, "this") 882 kind = self.sql(expression, "kind") 883 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 884 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 885 kind = f"{sep}{kind}" if kind else "" 886 constraints = f" {constraints}" if constraints else "" 887 position = self.sql(expression, "position") 888 position = f" {position}" if position else "" 889 890 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 891 kind = "" 892 893 return f"{exists}{column}{kind}{constraints}{position}" 894 895 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 896 this = self.sql(expression, "this") 897 kind_sql = self.sql(expression, "kind").strip() 898 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 899 900 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 901 this = self.sql(expression, "this") 902 if expression.args.get("not_null"): 903 persisted = " PERSISTED NOT NULL" 904 elif expression.args.get("persisted"): 905 persisted = " PERSISTED" 906 else: 907 persisted = "" 908 return f"AS {this}{persisted}" 909 910 def autoincrementcolumnconstraint_sql(self, _) -> str: 911 return self.token_sql(TokenType.AUTO_INCREMENT) 912 913 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 914 if isinstance(expression.this, list): 915 this = self.wrap(self.expressions(expression, key="this", flat=True)) 916 else: 917 this = self.sql(expression, "this") 918 919 return f"COMPRESS {this}" 920 921 def generatedasidentitycolumnconstraint_sql( 922 self, expression: exp.GeneratedAsIdentityColumnConstraint 923 ) -> str: 924 this = "" 925 if expression.this is not None: 926 on_null = " ON NULL" if expression.args.get("on_null") else "" 927 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 928 929 start = expression.args.get("start") 930 start = f"START WITH {start}" if start else "" 931 increment = expression.args.get("increment") 932 increment = f" INCREMENT BY {increment}" if increment else "" 933 minvalue = expression.args.get("minvalue") 934 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 935 maxvalue = expression.args.get("maxvalue") 936 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 937 cycle = expression.args.get("cycle") 938 cycle_sql = "" 939 940 if cycle is not None: 941 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 942 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 943 944 sequence_opts = "" 945 if start or increment or cycle_sql: 946 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 947 sequence_opts = f" ({sequence_opts.strip()})" 948 949 expr = self.sql(expression, "expression") 950 expr = f"({expr})" if expr else "IDENTITY" 951 952 return f"GENERATED{this} AS {expr}{sequence_opts}" 953 954 def generatedasrowcolumnconstraint_sql( 955 self, expression: exp.GeneratedAsRowColumnConstraint 956 ) -> str: 957 start = "START" if expression.args.get("start") else "END" 958 hidden = " HIDDEN" if expression.args.get("hidden") else "" 959 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 960 961 def periodforsystemtimeconstraint_sql( 962 self, expression: exp.PeriodForSystemTimeConstraint 963 ) -> str: 964 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 965 966 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 967 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 968 969 def transformcolumnconstraint_sql(self, expression: exp.TransformColumnConstraint) -> str: 970 return f"AS {self.sql(expression, 'this')}" 971 972 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 973 desc = expression.args.get("desc") 974 if desc is not None: 975 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 976 return "PRIMARY KEY" 977 978 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 979 this = self.sql(expression, "this") 980 this = f" {this}" if this else "" 981 index_type = expression.args.get("index_type") 982 index_type = f" USING {index_type}" if index_type else "" 983 on_conflict = self.sql(expression, "on_conflict") 984 on_conflict = f" {on_conflict}" if on_conflict else "" 985 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 986 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}" 987 988 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 989 return self.sql(expression, "this") 990 991 def create_sql(self, expression: exp.Create) -> str: 992 kind = self.sql(expression, "kind") 993 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 994 properties = expression.args.get("properties") 995 properties_locs = self.locate_properties(properties) if properties else defaultdict() 996 997 this = self.createable_sql(expression, properties_locs) 998 999 properties_sql = "" 1000 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1001 exp.Properties.Location.POST_WITH 1002 ): 1003 properties_sql = self.sql( 1004 exp.Properties( 1005 expressions=[ 1006 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1007 *properties_locs[exp.Properties.Location.POST_WITH], 1008 ] 1009 ) 1010 ) 1011 1012 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1013 properties_sql = self.sep() + properties_sql 1014 elif not self.pretty: 1015 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1016 properties_sql = f" {properties_sql}" 1017 1018 begin = " BEGIN" if expression.args.get("begin") else "" 1019 end = " END" if expression.args.get("end") else "" 1020 1021 expression_sql = self.sql(expression, "expression") 1022 if expression_sql: 1023 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1024 1025 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1026 postalias_props_sql = "" 1027 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1028 postalias_props_sql = self.properties( 1029 exp.Properties( 1030 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1031 ), 1032 wrapped=False, 1033 ) 1034 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1035 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1036 1037 postindex_props_sql = "" 1038 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1039 postindex_props_sql = self.properties( 1040 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1041 wrapped=False, 1042 prefix=" ", 1043 ) 1044 1045 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1046 indexes = f" {indexes}" if indexes else "" 1047 index_sql = indexes + postindex_props_sql 1048 1049 replace = " OR REPLACE" if expression.args.get("replace") else "" 1050 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1051 unique = " UNIQUE" if expression.args.get("unique") else "" 1052 1053 clustered = expression.args.get("clustered") 1054 if clustered is None: 1055 clustered_sql = "" 1056 elif clustered: 1057 clustered_sql = " CLUSTERED COLUMNSTORE" 1058 else: 1059 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1060 1061 postcreate_props_sql = "" 1062 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1063 postcreate_props_sql = self.properties( 1064 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1065 sep=" ", 1066 prefix=" ", 1067 wrapped=False, 1068 ) 1069 1070 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1071 1072 postexpression_props_sql = "" 1073 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1074 postexpression_props_sql = self.properties( 1075 exp.Properties( 1076 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1077 ), 1078 sep=" ", 1079 prefix=" ", 1080 wrapped=False, 1081 ) 1082 1083 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1084 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1085 no_schema_binding = ( 1086 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1087 ) 1088 1089 clone = self.sql(expression, "clone") 1090 clone = f" {clone}" if clone else "" 1091 1092 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_sql}{expression_sql}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1093 return self.prepend_ctes(expression, expression_sql) 1094 1095 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1096 start = self.sql(expression, "start") 1097 start = f"START WITH {start}" if start else "" 1098 increment = self.sql(expression, "increment") 1099 increment = f" INCREMENT BY {increment}" if increment else "" 1100 minvalue = self.sql(expression, "minvalue") 1101 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1102 maxvalue = self.sql(expression, "maxvalue") 1103 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1104 owned = self.sql(expression, "owned") 1105 owned = f" OWNED BY {owned}" if owned else "" 1106 1107 cache = expression.args.get("cache") 1108 if cache is None: 1109 cache_str = "" 1110 elif cache is True: 1111 cache_str = " CACHE" 1112 else: 1113 cache_str = f" CACHE {cache}" 1114 1115 options = self.expressions(expression, key="options", flat=True, sep=" ") 1116 options = f" {options}" if options else "" 1117 1118 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1119 1120 def clone_sql(self, expression: exp.Clone) -> str: 1121 this = self.sql(expression, "this") 1122 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1123 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1124 return f"{shallow}{keyword} {this}" 1125 1126 def describe_sql(self, expression: exp.Describe) -> str: 1127 style = expression.args.get("style") 1128 style = f" {style}" if style else "" 1129 partition = self.sql(expression, "partition") 1130 partition = f" {partition}" if partition else "" 1131 return f"DESCRIBE{style} {self.sql(expression, 'this')}{partition}" 1132 1133 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1134 tag = self.sql(expression, "tag") 1135 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1136 1137 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1138 with_ = self.sql(expression, "with") 1139 if with_: 1140 sql = f"{with_}{self.sep()}{sql}" 1141 return sql 1142 1143 def with_sql(self, expression: exp.With) -> str: 1144 sql = self.expressions(expression, flat=True) 1145 recursive = ( 1146 "RECURSIVE " 1147 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1148 else "" 1149 ) 1150 1151 return f"WITH {recursive}{sql}" 1152 1153 def cte_sql(self, expression: exp.CTE) -> str: 1154 alias = self.sql(expression, "alias") 1155 1156 materialized = expression.args.get("materialized") 1157 if materialized is False: 1158 materialized = "NOT MATERIALIZED " 1159 elif materialized: 1160 materialized = "MATERIALIZED " 1161 1162 return f"{alias} AS {materialized or ''}{self.wrap(expression)}" 1163 1164 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1165 alias = self.sql(expression, "this") 1166 columns = self.expressions(expression, key="columns", flat=True) 1167 columns = f"({columns})" if columns else "" 1168 1169 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1170 columns = "" 1171 self.unsupported("Named columns are not supported in table alias.") 1172 1173 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1174 alias = self._next_name() 1175 1176 return f"{alias}{columns}" 1177 1178 def bitstring_sql(self, expression: exp.BitString) -> str: 1179 this = self.sql(expression, "this") 1180 if self.dialect.BIT_START: 1181 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1182 return f"{int(this, 2)}" 1183 1184 def hexstring_sql(self, expression: exp.HexString) -> str: 1185 this = self.sql(expression, "this") 1186 if self.dialect.HEX_START: 1187 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1188 return f"{int(this, 16)}" 1189 1190 def bytestring_sql(self, expression: exp.ByteString) -> str: 1191 this = self.sql(expression, "this") 1192 if self.dialect.BYTE_START: 1193 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1194 return this 1195 1196 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1197 this = self.sql(expression, "this") 1198 escape = expression.args.get("escape") 1199 1200 if self.dialect.UNICODE_START: 1201 escape_substitute = r"\\\1" 1202 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1203 else: 1204 escape_substitute = r"\\u\1" 1205 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1206 1207 if escape: 1208 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1209 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1210 else: 1211 escape_pattern = ESCAPED_UNICODE_RE 1212 escape_sql = "" 1213 1214 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1215 this = escape_pattern.sub(escape_substitute, this) 1216 1217 return f"{left_quote}{this}{right_quote}{escape_sql}" 1218 1219 def rawstring_sql(self, expression: exp.RawString) -> str: 1220 string = self.escape_str(expression.this.replace("\\", "\\\\"), escape_backslash=False) 1221 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1222 1223 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1224 this = self.sql(expression, "this") 1225 specifier = self.sql(expression, "expression") 1226 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1227 return f"{this}{specifier}" 1228 1229 def datatype_sql(self, expression: exp.DataType) -> str: 1230 nested = "" 1231 values = "" 1232 interior = self.expressions(expression, flat=True) 1233 1234 type_value = expression.this 1235 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1236 type_sql = self.sql(expression, "kind") 1237 elif type_value != exp.DataType.Type.NULLABLE or self.SUPPORTS_NULLABLE_TYPES: 1238 type_sql = ( 1239 self.TYPE_MAPPING.get(type_value, type_value.value) 1240 if isinstance(type_value, exp.DataType.Type) 1241 else type_value 1242 ) 1243 else: 1244 return interior 1245 1246 if interior: 1247 if expression.args.get("nested"): 1248 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1249 if expression.args.get("values") is not None: 1250 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1251 values = self.expressions(expression, key="values", flat=True) 1252 values = f"{delimiters[0]}{values}{delimiters[1]}" 1253 elif type_value == exp.DataType.Type.INTERVAL: 1254 nested = f" {interior}" 1255 else: 1256 nested = f"({interior})" 1257 1258 type_sql = f"{type_sql}{nested}{values}" 1259 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1260 exp.DataType.Type.TIMETZ, 1261 exp.DataType.Type.TIMESTAMPTZ, 1262 ): 1263 type_sql = f"{type_sql} WITH TIME ZONE" 1264 1265 return type_sql 1266 1267 def directory_sql(self, expression: exp.Directory) -> str: 1268 local = "LOCAL " if expression.args.get("local") else "" 1269 row_format = self.sql(expression, "row_format") 1270 row_format = f" {row_format}" if row_format else "" 1271 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1272 1273 def delete_sql(self, expression: exp.Delete) -> str: 1274 this = self.sql(expression, "this") 1275 this = f" FROM {this}" if this else "" 1276 using = self.sql(expression, "using") 1277 using = f" USING {using}" if using else "" 1278 where = self.sql(expression, "where") 1279 returning = self.sql(expression, "returning") 1280 limit = self.sql(expression, "limit") 1281 tables = self.expressions(expression, key="tables") 1282 tables = f" {tables}" if tables else "" 1283 if self.RETURNING_END: 1284 expression_sql = f"{this}{using}{where}{returning}{limit}" 1285 else: 1286 expression_sql = f"{returning}{this}{using}{where}{limit}" 1287 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1288 1289 def drop_sql(self, expression: exp.Drop) -> str: 1290 this = self.sql(expression, "this") 1291 expressions = self.expressions(expression, flat=True) 1292 expressions = f" ({expressions})" if expressions else "" 1293 kind = expression.args["kind"] 1294 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1295 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1296 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1297 on_cluster = self.sql(expression, "cluster") 1298 on_cluster = f" {on_cluster}" if on_cluster else "" 1299 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1300 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1301 cascade = " CASCADE" if expression.args.get("cascade") else "" 1302 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1303 purge = " PURGE" if expression.args.get("purge") else "" 1304 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1305 1306 def set_operation(self, expression: exp.SetOperation) -> str: 1307 op_type = type(expression) 1308 op_name = op_type.key.upper() 1309 1310 distinct = expression.args.get("distinct") 1311 if ( 1312 distinct is False 1313 and op_type in (exp.Except, exp.Intersect) 1314 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1315 ): 1316 self.unsupported(f"{op_name} ALL is not supported") 1317 1318 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1319 1320 if distinct is None: 1321 distinct = default_distinct 1322 if distinct is None: 1323 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1324 1325 if distinct is default_distinct: 1326 kind = "" 1327 else: 1328 kind = " DISTINCT" if distinct else " ALL" 1329 1330 by_name = " BY NAME" if expression.args.get("by_name") else "" 1331 return f"{op_name}{kind}{by_name}" 1332 1333 def set_operations(self, expression: exp.SetOperation) -> str: 1334 if not self.SET_OP_MODIFIERS: 1335 limit = expression.args.get("limit") 1336 order = expression.args.get("order") 1337 1338 if limit or order: 1339 select = exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1340 1341 if limit: 1342 select = select.limit(limit.pop(), copy=False) 1343 if order: 1344 select = select.order_by(order.pop(), copy=False) 1345 return self.sql(select) 1346 1347 sqls: t.List[str] = [] 1348 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1349 1350 while stack: 1351 node = stack.pop() 1352 1353 if isinstance(node, exp.SetOperation): 1354 stack.append(node.expression) 1355 stack.append( 1356 self.maybe_comment( 1357 self.set_operation(node), comments=node.comments, separated=True 1358 ) 1359 ) 1360 stack.append(node.this) 1361 else: 1362 sqls.append(self.sql(node)) 1363 1364 this = self.sep().join(sqls) 1365 this = self.query_modifiers(expression, this) 1366 return self.prepend_ctes(expression, this) 1367 1368 def fetch_sql(self, expression: exp.Fetch) -> str: 1369 direction = expression.args.get("direction") 1370 direction = f" {direction}" if direction else "" 1371 count = self.sql(expression, "count") 1372 count = f" {count}" if count else "" 1373 if expression.args.get("percent"): 1374 count = f"{count} PERCENT" 1375 with_ties_or_only = "WITH TIES" if expression.args.get("with_ties") else "ONLY" 1376 return f"{self.seg('FETCH')}{direction}{count} ROWS {with_ties_or_only}" 1377 1378 def filter_sql(self, expression: exp.Filter) -> str: 1379 if self.AGGREGATE_FILTER_SUPPORTED: 1380 this = self.sql(expression, "this") 1381 where = self.sql(expression, "expression").strip() 1382 return f"{this} FILTER({where})" 1383 1384 agg = expression.this 1385 agg_arg = agg.this 1386 cond = expression.expression.this 1387 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1388 return self.sql(agg) 1389 1390 def hint_sql(self, expression: exp.Hint) -> str: 1391 if not self.QUERY_HINTS: 1392 self.unsupported("Hints are not supported") 1393 return "" 1394 1395 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1396 1397 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1398 using = self.sql(expression, "using") 1399 using = f" USING {using}" if using else "" 1400 columns = self.expressions(expression, key="columns", flat=True) 1401 columns = f"({columns})" if columns else "" 1402 partition_by = self.expressions(expression, key="partition_by", flat=True) 1403 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1404 where = self.sql(expression, "where") 1405 include = self.expressions(expression, key="include", flat=True) 1406 if include: 1407 include = f" INCLUDE ({include})" 1408 with_storage = self.expressions(expression, key="with_storage", flat=True) 1409 with_storage = f" WITH ({with_storage})" if with_storage else "" 1410 tablespace = self.sql(expression, "tablespace") 1411 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1412 on = self.sql(expression, "on") 1413 on = f" ON {on}" if on else "" 1414 1415 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1416 1417 def index_sql(self, expression: exp.Index) -> str: 1418 unique = "UNIQUE " if expression.args.get("unique") else "" 1419 primary = "PRIMARY " if expression.args.get("primary") else "" 1420 amp = "AMP " if expression.args.get("amp") else "" 1421 name = self.sql(expression, "this") 1422 name = f"{name} " if name else "" 1423 table = self.sql(expression, "table") 1424 table = f"{self.INDEX_ON} {table}" if table else "" 1425 1426 index = "INDEX " if not table else "" 1427 1428 params = self.sql(expression, "params") 1429 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1430 1431 def identifier_sql(self, expression: exp.Identifier) -> str: 1432 text = expression.name 1433 lower = text.lower() 1434 text = lower if self.normalize and not expression.quoted else text 1435 text = text.replace(self.dialect.IDENTIFIER_END, self._escaped_identifier_end) 1436 if ( 1437 expression.quoted 1438 or self.dialect.can_identify(text, self.identify) 1439 or lower in self.RESERVED_KEYWORDS 1440 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1441 ): 1442 text = f"{self.dialect.IDENTIFIER_START}{text}{self.dialect.IDENTIFIER_END}" 1443 return text 1444 1445 def hex_sql(self, expression: exp.Hex) -> str: 1446 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1447 if self.dialect.HEX_LOWERCASE: 1448 text = self.func("LOWER", text) 1449 1450 return text 1451 1452 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1453 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1454 if not self.dialect.HEX_LOWERCASE: 1455 text = self.func("LOWER", text) 1456 return text 1457 1458 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1459 input_format = self.sql(expression, "input_format") 1460 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1461 output_format = self.sql(expression, "output_format") 1462 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1463 return self.sep().join((input_format, output_format)) 1464 1465 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1466 string = self.sql(exp.Literal.string(expression.name)) 1467 return f"{prefix}{string}" 1468 1469 def partition_sql(self, expression: exp.Partition) -> str: 1470 return f"PARTITION({self.expressions(expression, flat=True)})" 1471 1472 def properties_sql(self, expression: exp.Properties) -> str: 1473 root_properties = [] 1474 with_properties = [] 1475 1476 for p in expression.expressions: 1477 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1478 if p_loc == exp.Properties.Location.POST_WITH: 1479 with_properties.append(p) 1480 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1481 root_properties.append(p) 1482 1483 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1484 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1485 1486 if root_props and with_props and not self.pretty: 1487 with_props = " " + with_props 1488 1489 return root_props + with_props 1490 1491 def root_properties(self, properties: exp.Properties) -> str: 1492 if properties.expressions: 1493 return self.expressions(properties, indent=False, sep=" ") 1494 return "" 1495 1496 def properties( 1497 self, 1498 properties: exp.Properties, 1499 prefix: str = "", 1500 sep: str = ", ", 1501 suffix: str = "", 1502 wrapped: bool = True, 1503 ) -> str: 1504 if properties.expressions: 1505 expressions = self.expressions(properties, sep=sep, indent=False) 1506 if expressions: 1507 expressions = self.wrap(expressions) if wrapped else expressions 1508 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1509 return "" 1510 1511 def with_properties(self, properties: exp.Properties) -> str: 1512 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1513 1514 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1515 properties_locs = defaultdict(list) 1516 for p in properties.expressions: 1517 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1518 if p_loc != exp.Properties.Location.UNSUPPORTED: 1519 properties_locs[p_loc].append(p) 1520 else: 1521 self.unsupported(f"Unsupported property {p.key}") 1522 1523 return properties_locs 1524 1525 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1526 if isinstance(expression.this, exp.Dot): 1527 return self.sql(expression, "this") 1528 return f"'{expression.name}'" if string_key else expression.name 1529 1530 def property_sql(self, expression: exp.Property) -> str: 1531 property_cls = expression.__class__ 1532 if property_cls == exp.Property: 1533 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1534 1535 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1536 if not property_name: 1537 self.unsupported(f"Unsupported property {expression.key}") 1538 1539 return f"{property_name}={self.sql(expression, 'this')}" 1540 1541 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1542 if self.SUPPORTS_CREATE_TABLE_LIKE: 1543 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1544 options = f" {options}" if options else "" 1545 1546 like = f"LIKE {self.sql(expression, 'this')}{options}" 1547 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1548 like = f"({like})" 1549 1550 return like 1551 1552 if expression.expressions: 1553 self.unsupported("Transpilation of LIKE property options is unsupported") 1554 1555 select = exp.select("*").from_(expression.this).limit(0) 1556 return f"AS {self.sql(select)}" 1557 1558 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1559 no = "NO " if expression.args.get("no") else "" 1560 protection = " PROTECTION" if expression.args.get("protection") else "" 1561 return f"{no}FALLBACK{protection}" 1562 1563 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1564 no = "NO " if expression.args.get("no") else "" 1565 local = expression.args.get("local") 1566 local = f"{local} " if local else "" 1567 dual = "DUAL " if expression.args.get("dual") else "" 1568 before = "BEFORE " if expression.args.get("before") else "" 1569 after = "AFTER " if expression.args.get("after") else "" 1570 return f"{no}{local}{dual}{before}{after}JOURNAL" 1571 1572 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1573 freespace = self.sql(expression, "this") 1574 percent = " PERCENT" if expression.args.get("percent") else "" 1575 return f"FREESPACE={freespace}{percent}" 1576 1577 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1578 if expression.args.get("default"): 1579 property = "DEFAULT" 1580 elif expression.args.get("on"): 1581 property = "ON" 1582 else: 1583 property = "OFF" 1584 return f"CHECKSUM={property}" 1585 1586 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1587 if expression.args.get("no"): 1588 return "NO MERGEBLOCKRATIO" 1589 if expression.args.get("default"): 1590 return "DEFAULT MERGEBLOCKRATIO" 1591 1592 percent = " PERCENT" if expression.args.get("percent") else "" 1593 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1594 1595 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1596 default = expression.args.get("default") 1597 minimum = expression.args.get("minimum") 1598 maximum = expression.args.get("maximum") 1599 if default or minimum or maximum: 1600 if default: 1601 prop = "DEFAULT" 1602 elif minimum: 1603 prop = "MINIMUM" 1604 else: 1605 prop = "MAXIMUM" 1606 return f"{prop} DATABLOCKSIZE" 1607 units = expression.args.get("units") 1608 units = f" {units}" if units else "" 1609 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1610 1611 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1612 autotemp = expression.args.get("autotemp") 1613 always = expression.args.get("always") 1614 default = expression.args.get("default") 1615 manual = expression.args.get("manual") 1616 never = expression.args.get("never") 1617 1618 if autotemp is not None: 1619 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1620 elif always: 1621 prop = "ALWAYS" 1622 elif default: 1623 prop = "DEFAULT" 1624 elif manual: 1625 prop = "MANUAL" 1626 elif never: 1627 prop = "NEVER" 1628 return f"BLOCKCOMPRESSION={prop}" 1629 1630 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1631 no = expression.args.get("no") 1632 no = " NO" if no else "" 1633 concurrent = expression.args.get("concurrent") 1634 concurrent = " CONCURRENT" if concurrent else "" 1635 target = self.sql(expression, "target") 1636 target = f" {target}" if target else "" 1637 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1638 1639 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1640 if isinstance(expression.this, list): 1641 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1642 if expression.this: 1643 modulus = self.sql(expression, "this") 1644 remainder = self.sql(expression, "expression") 1645 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1646 1647 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1648 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1649 return f"FROM ({from_expressions}) TO ({to_expressions})" 1650 1651 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1652 this = self.sql(expression, "this") 1653 1654 for_values_or_default = expression.expression 1655 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1656 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1657 else: 1658 for_values_or_default = " DEFAULT" 1659 1660 return f"PARTITION OF {this}{for_values_or_default}" 1661 1662 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1663 kind = expression.args.get("kind") 1664 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1665 for_or_in = expression.args.get("for_or_in") 1666 for_or_in = f" {for_or_in}" if for_or_in else "" 1667 lock_type = expression.args.get("lock_type") 1668 override = " OVERRIDE" if expression.args.get("override") else "" 1669 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1670 1671 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1672 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1673 statistics = expression.args.get("statistics") 1674 statistics_sql = "" 1675 if statistics is not None: 1676 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1677 return f"{data_sql}{statistics_sql}" 1678 1679 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1680 this = self.sql(expression, "this") 1681 this = f"HISTORY_TABLE={this}" if this else "" 1682 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1683 data_consistency = ( 1684 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1685 ) 1686 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1687 retention_period = ( 1688 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1689 ) 1690 1691 if this: 1692 on_sql = self.func("ON", this, data_consistency, retention_period) 1693 else: 1694 on_sql = "ON" if expression.args.get("on") else "OFF" 1695 1696 sql = f"SYSTEM_VERSIONING={on_sql}" 1697 1698 return f"WITH({sql})" if expression.args.get("with") else sql 1699 1700 def insert_sql(self, expression: exp.Insert) -> str: 1701 hint = self.sql(expression, "hint") 1702 overwrite = expression.args.get("overwrite") 1703 1704 if isinstance(expression.this, exp.Directory): 1705 this = " OVERWRITE" if overwrite else " INTO" 1706 else: 1707 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1708 1709 stored = self.sql(expression, "stored") 1710 stored = f" {stored}" if stored else "" 1711 alternative = expression.args.get("alternative") 1712 alternative = f" OR {alternative}" if alternative else "" 1713 ignore = " IGNORE" if expression.args.get("ignore") else "" 1714 is_function = expression.args.get("is_function") 1715 if is_function: 1716 this = f"{this} FUNCTION" 1717 this = f"{this} {self.sql(expression, 'this')}" 1718 1719 exists = " IF EXISTS" if expression.args.get("exists") else "" 1720 where = self.sql(expression, "where") 1721 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1722 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1723 on_conflict = self.sql(expression, "conflict") 1724 on_conflict = f" {on_conflict}" if on_conflict else "" 1725 by_name = " BY NAME" if expression.args.get("by_name") else "" 1726 returning = self.sql(expression, "returning") 1727 1728 if self.RETURNING_END: 1729 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1730 else: 1731 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1732 1733 partition_by = self.sql(expression, "partition") 1734 partition_by = f" {partition_by}" if partition_by else "" 1735 settings = self.sql(expression, "settings") 1736 settings = f" {settings}" if settings else "" 1737 1738 source = self.sql(expression, "source") 1739 source = f"TABLE {source}" if source else "" 1740 1741 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1742 return self.prepend_ctes(expression, sql) 1743 1744 def introducer_sql(self, expression: exp.Introducer) -> str: 1745 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1746 1747 def kill_sql(self, expression: exp.Kill) -> str: 1748 kind = self.sql(expression, "kind") 1749 kind = f" {kind}" if kind else "" 1750 this = self.sql(expression, "this") 1751 this = f" {this}" if this else "" 1752 return f"KILL{kind}{this}" 1753 1754 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1755 return expression.name 1756 1757 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1758 return expression.name 1759 1760 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1761 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1762 1763 constraint = self.sql(expression, "constraint") 1764 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1765 1766 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1767 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1768 action = self.sql(expression, "action") 1769 1770 expressions = self.expressions(expression, flat=True) 1771 if expressions: 1772 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1773 expressions = f" {set_keyword}{expressions}" 1774 1775 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}" 1776 1777 def returning_sql(self, expression: exp.Returning) -> str: 1778 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1779 1780 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1781 fields = self.sql(expression, "fields") 1782 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1783 escaped = self.sql(expression, "escaped") 1784 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1785 items = self.sql(expression, "collection_items") 1786 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1787 keys = self.sql(expression, "map_keys") 1788 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1789 lines = self.sql(expression, "lines") 1790 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1791 null = self.sql(expression, "null") 1792 null = f" NULL DEFINED AS {null}" if null else "" 1793 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1794 1795 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1796 return f"WITH ({self.expressions(expression, flat=True)})" 1797 1798 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1799 this = f"{self.sql(expression, 'this')} INDEX" 1800 target = self.sql(expression, "target") 1801 target = f" FOR {target}" if target else "" 1802 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1803 1804 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1805 this = self.sql(expression, "this") 1806 kind = self.sql(expression, "kind") 1807 expr = self.sql(expression, "expression") 1808 return f"{this} ({kind} => {expr})" 1809 1810 def table_parts(self, expression: exp.Table) -> str: 1811 return ".".join( 1812 self.sql(part) 1813 for part in ( 1814 expression.args.get("catalog"), 1815 expression.args.get("db"), 1816 expression.args.get("this"), 1817 ) 1818 if part is not None 1819 ) 1820 1821 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 1822 table = self.table_parts(expression) 1823 only = "ONLY " if expression.args.get("only") else "" 1824 partition = self.sql(expression, "partition") 1825 partition = f" {partition}" if partition else "" 1826 version = self.sql(expression, "version") 1827 version = f" {version}" if version else "" 1828 alias = self.sql(expression, "alias") 1829 alias = f"{sep}{alias}" if alias else "" 1830 1831 sample = self.sql(expression, "sample") 1832 if self.dialect.ALIAS_POST_TABLESAMPLE: 1833 sample_pre_alias = sample 1834 sample_post_alias = "" 1835 else: 1836 sample_pre_alias = "" 1837 sample_post_alias = sample 1838 1839 hints = self.expressions(expression, key="hints", sep=" ") 1840 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 1841 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 1842 joins = self.indent( 1843 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 1844 ) 1845 laterals = self.expressions(expression, key="laterals", sep="") 1846 1847 file_format = self.sql(expression, "format") 1848 if file_format: 1849 pattern = self.sql(expression, "pattern") 1850 pattern = f", PATTERN => {pattern}" if pattern else "" 1851 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 1852 1853 ordinality = expression.args.get("ordinality") or "" 1854 if ordinality: 1855 ordinality = f" WITH ORDINALITY{alias}" 1856 alias = "" 1857 1858 when = self.sql(expression, "when") 1859 if when: 1860 table = f"{table} {when}" 1861 1862 changes = self.sql(expression, "changes") 1863 changes = f" {changes}" if changes else "" 1864 1865 rows_from = self.expressions(expression, key="rows_from") 1866 if rows_from: 1867 table = f"ROWS FROM {self.wrap(rows_from)}" 1868 1869 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 1870 1871 def tablesample_sql( 1872 self, 1873 expression: exp.TableSample, 1874 tablesample_keyword: t.Optional[str] = None, 1875 ) -> str: 1876 method = self.sql(expression, "method") 1877 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 1878 numerator = self.sql(expression, "bucket_numerator") 1879 denominator = self.sql(expression, "bucket_denominator") 1880 field = self.sql(expression, "bucket_field") 1881 field = f" ON {field}" if field else "" 1882 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 1883 seed = self.sql(expression, "seed") 1884 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 1885 1886 size = self.sql(expression, "size") 1887 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 1888 size = f"{size} ROWS" 1889 1890 percent = self.sql(expression, "percent") 1891 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 1892 percent = f"{percent} PERCENT" 1893 1894 expr = f"{bucket}{percent}{size}" 1895 if self.TABLESAMPLE_REQUIRES_PARENS: 1896 expr = f"({expr})" 1897 1898 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 1899 1900 def pivot_sql(self, expression: exp.Pivot) -> str: 1901 expressions = self.expressions(expression, flat=True) 1902 1903 if expression.this: 1904 this = self.sql(expression, "this") 1905 if not expressions: 1906 return f"UNPIVOT {this}" 1907 1908 on = f"{self.seg('ON')} {expressions}" 1909 using = self.expressions(expression, key="using", flat=True) 1910 using = f"{self.seg('USING')} {using}" if using else "" 1911 group = self.sql(expression, "group") 1912 return f"PIVOT {this}{on}{using}{group}" 1913 1914 alias = self.sql(expression, "alias") 1915 alias = f" AS {alias}" if alias else "" 1916 direction = self.seg("UNPIVOT" if expression.unpivot else "PIVOT") 1917 1918 field = self.sql(expression, "field") 1919 if field and isinstance(expression.args.get("field"), exp.PivotAny): 1920 field = f"IN ({field})" 1921 1922 include_nulls = expression.args.get("include_nulls") 1923 if include_nulls is not None: 1924 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 1925 else: 1926 nulls = "" 1927 1928 default_on_null = self.sql(expression, "default_on_null") 1929 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 1930 return f"{direction}{nulls}({expressions} FOR {field}{default_on_null}){alias}" 1931 1932 def version_sql(self, expression: exp.Version) -> str: 1933 this = f"FOR {expression.name}" 1934 kind = expression.text("kind") 1935 expr = self.sql(expression, "expression") 1936 return f"{this} {kind} {expr}" 1937 1938 def tuple_sql(self, expression: exp.Tuple) -> str: 1939 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 1940 1941 def update_sql(self, expression: exp.Update) -> str: 1942 this = self.sql(expression, "this") 1943 set_sql = self.expressions(expression, flat=True) 1944 from_sql = self.sql(expression, "from") 1945 where_sql = self.sql(expression, "where") 1946 returning = self.sql(expression, "returning") 1947 order = self.sql(expression, "order") 1948 limit = self.sql(expression, "limit") 1949 if self.RETURNING_END: 1950 expression_sql = f"{from_sql}{where_sql}{returning}" 1951 else: 1952 expression_sql = f"{returning}{from_sql}{where_sql}" 1953 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 1954 return self.prepend_ctes(expression, sql) 1955 1956 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 1957 values_as_table = values_as_table and self.VALUES_AS_TABLE 1958 1959 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 1960 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 1961 args = self.expressions(expression) 1962 alias = self.sql(expression, "alias") 1963 values = f"VALUES{self.seg('')}{args}" 1964 values = ( 1965 f"({values})" 1966 if self.WRAP_DERIVED_VALUES and (alias or isinstance(expression.parent, exp.From)) 1967 else values 1968 ) 1969 return f"{values} AS {alias}" if alias else values 1970 1971 # Converts `VALUES...` expression into a series of select unions. 1972 alias_node = expression.args.get("alias") 1973 column_names = alias_node and alias_node.columns 1974 1975 selects: t.List[exp.Query] = [] 1976 1977 for i, tup in enumerate(expression.expressions): 1978 row = tup.expressions 1979 1980 if i == 0 and column_names: 1981 row = [ 1982 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 1983 ] 1984 1985 selects.append(exp.Select(expressions=row)) 1986 1987 if self.pretty: 1988 # This may result in poor performance for large-cardinality `VALUES` tables, due to 1989 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 1990 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 1991 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 1992 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 1993 1994 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 1995 unions = " UNION ALL ".join(self.sql(select) for select in selects) 1996 return f"({unions}){alias}" 1997 1998 def var_sql(self, expression: exp.Var) -> str: 1999 return self.sql(expression, "this") 2000 2001 def into_sql(self, expression: exp.Into) -> str: 2002 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2003 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2004 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2005 2006 def from_sql(self, expression: exp.From) -> str: 2007 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2008 2009 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2010 grouping_sets = self.expressions(expression, indent=False) 2011 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2012 2013 def rollup_sql(self, expression: exp.Rollup) -> str: 2014 expressions = self.expressions(expression, indent=False) 2015 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2016 2017 def cube_sql(self, expression: exp.Cube) -> str: 2018 expressions = self.expressions(expression, indent=False) 2019 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2020 2021 def group_sql(self, expression: exp.Group) -> str: 2022 group_by_all = expression.args.get("all") 2023 if group_by_all is True: 2024 modifier = " ALL" 2025 elif group_by_all is False: 2026 modifier = " DISTINCT" 2027 else: 2028 modifier = "" 2029 2030 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2031 2032 grouping_sets = self.expressions(expression, key="grouping_sets") 2033 cube = self.expressions(expression, key="cube") 2034 rollup = self.expressions(expression, key="rollup") 2035 2036 groupings = csv( 2037 self.seg(grouping_sets) if grouping_sets else "", 2038 self.seg(cube) if cube else "", 2039 self.seg(rollup) if rollup else "", 2040 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2041 sep=self.GROUPINGS_SEP, 2042 ) 2043 2044 if ( 2045 expression.expressions 2046 and groupings 2047 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2048 ): 2049 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2050 2051 return f"{group_by}{groupings}" 2052 2053 def having_sql(self, expression: exp.Having) -> str: 2054 this = self.indent(self.sql(expression, "this")) 2055 return f"{self.seg('HAVING')}{self.sep()}{this}" 2056 2057 def connect_sql(self, expression: exp.Connect) -> str: 2058 start = self.sql(expression, "start") 2059 start = self.seg(f"START WITH {start}") if start else "" 2060 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2061 connect = self.sql(expression, "connect") 2062 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2063 return start + connect 2064 2065 def prior_sql(self, expression: exp.Prior) -> str: 2066 return f"PRIOR {self.sql(expression, 'this')}" 2067 2068 def join_sql(self, expression: exp.Join) -> str: 2069 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2070 side = None 2071 else: 2072 side = expression.side 2073 2074 op_sql = " ".join( 2075 op 2076 for op in ( 2077 expression.method, 2078 "GLOBAL" if expression.args.get("global") else None, 2079 side, 2080 expression.kind, 2081 expression.hint if self.JOIN_HINTS else None, 2082 ) 2083 if op 2084 ) 2085 match_cond = self.sql(expression, "match_condition") 2086 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2087 on_sql = self.sql(expression, "on") 2088 using = expression.args.get("using") 2089 2090 if not on_sql and using: 2091 on_sql = csv(*(self.sql(column) for column in using)) 2092 2093 this = expression.this 2094 this_sql = self.sql(this) 2095 2096 if on_sql: 2097 on_sql = self.indent(on_sql, skip_first=True) 2098 space = self.seg(" " * self.pad) if self.pretty else " " 2099 if using: 2100 on_sql = f"{space}USING ({on_sql})" 2101 else: 2102 on_sql = f"{space}ON {on_sql}" 2103 elif not op_sql: 2104 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2105 return f" {this_sql}" 2106 2107 return f", {this_sql}" 2108 2109 if op_sql != "STRAIGHT_JOIN": 2110 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2111 2112 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}" 2113 2114 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str: 2115 args = self.expressions(expression, flat=True) 2116 args = f"({args})" if len(args.split(",")) > 1 else args 2117 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2118 2119 def lateral_op(self, expression: exp.Lateral) -> str: 2120 cross_apply = expression.args.get("cross_apply") 2121 2122 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2123 if cross_apply is True: 2124 op = "INNER JOIN " 2125 elif cross_apply is False: 2126 op = "LEFT JOIN " 2127 else: 2128 op = "" 2129 2130 return f"{op}LATERAL" 2131 2132 def lateral_sql(self, expression: exp.Lateral) -> str: 2133 this = self.sql(expression, "this") 2134 2135 if expression.args.get("view"): 2136 alias = expression.args["alias"] 2137 columns = self.expressions(alias, key="columns", flat=True) 2138 table = f" {alias.name}" if alias.name else "" 2139 columns = f" AS {columns}" if columns else "" 2140 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2141 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2142 2143 alias = self.sql(expression, "alias") 2144 alias = f" AS {alias}" if alias else "" 2145 return f"{self.lateral_op(expression)} {this}{alias}" 2146 2147 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2148 this = self.sql(expression, "this") 2149 2150 args = [ 2151 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2152 for e in (expression.args.get(k) for k in ("offset", "expression")) 2153 if e 2154 ] 2155 2156 args_sql = ", ".join(self.sql(e) for e in args) 2157 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2158 expressions = self.expressions(expression, flat=True) 2159 expressions = f" BY {expressions}" if expressions else "" 2160 2161 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{expressions}" 2162 2163 def offset_sql(self, expression: exp.Offset) -> str: 2164 this = self.sql(expression, "this") 2165 value = expression.expression 2166 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2167 expressions = self.expressions(expression, flat=True) 2168 expressions = f" BY {expressions}" if expressions else "" 2169 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2170 2171 def setitem_sql(self, expression: exp.SetItem) -> str: 2172 kind = self.sql(expression, "kind") 2173 kind = f"{kind} " if kind else "" 2174 this = self.sql(expression, "this") 2175 expressions = self.expressions(expression) 2176 collate = self.sql(expression, "collate") 2177 collate = f" COLLATE {collate}" if collate else "" 2178 global_ = "GLOBAL " if expression.args.get("global") else "" 2179 return f"{global_}{kind}{this}{expressions}{collate}" 2180 2181 def set_sql(self, expression: exp.Set) -> str: 2182 expressions = ( 2183 f" {self.expressions(expression, flat=True)}" if expression.expressions else "" 2184 ) 2185 tag = " TAG" if expression.args.get("tag") else "" 2186 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2187 2188 def pragma_sql(self, expression: exp.Pragma) -> str: 2189 return f"PRAGMA {self.sql(expression, 'this')}" 2190 2191 def lock_sql(self, expression: exp.Lock) -> str: 2192 if not self.LOCKING_READS_SUPPORTED: 2193 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2194 return "" 2195 2196 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 2197 expressions = self.expressions(expression, flat=True) 2198 expressions = f" OF {expressions}" if expressions else "" 2199 wait = expression.args.get("wait") 2200 2201 if wait is not None: 2202 if isinstance(wait, exp.Literal): 2203 wait = f" WAIT {self.sql(wait)}" 2204 else: 2205 wait = " NOWAIT" if wait else " SKIP LOCKED" 2206 2207 return f"{lock_type}{expressions}{wait or ''}" 2208 2209 def literal_sql(self, expression: exp.Literal) -> str: 2210 text = expression.this or "" 2211 if expression.is_string: 2212 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2213 return text 2214 2215 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2216 if self.dialect.ESCAPED_SEQUENCES: 2217 to_escaped = self.dialect.ESCAPED_SEQUENCES 2218 text = "".join( 2219 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2220 ) 2221 2222 return self._replace_line_breaks(text).replace( 2223 self.dialect.QUOTE_END, self._escaped_quote_end 2224 ) 2225 2226 def loaddata_sql(self, expression: exp.LoadData) -> str: 2227 local = " LOCAL" if expression.args.get("local") else "" 2228 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2229 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2230 this = f" INTO TABLE {self.sql(expression, 'this')}" 2231 partition = self.sql(expression, "partition") 2232 partition = f" {partition}" if partition else "" 2233 input_format = self.sql(expression, "input_format") 2234 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2235 serde = self.sql(expression, "serde") 2236 serde = f" SERDE {serde}" if serde else "" 2237 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2238 2239 def null_sql(self, *_) -> str: 2240 return "NULL" 2241 2242 def boolean_sql(self, expression: exp.Boolean) -> str: 2243 return "TRUE" if expression.this else "FALSE" 2244 2245 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2246 this = self.sql(expression, "this") 2247 this = f"{this} " if this else this 2248 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2249 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2250 2251 def withfill_sql(self, expression: exp.WithFill) -> str: 2252 from_sql = self.sql(expression, "from") 2253 from_sql = f" FROM {from_sql}" if from_sql else "" 2254 to_sql = self.sql(expression, "to") 2255 to_sql = f" TO {to_sql}" if to_sql else "" 2256 step_sql = self.sql(expression, "step") 2257 step_sql = f" STEP {step_sql}" if step_sql else "" 2258 interpolated_values = [ 2259 f"{self.sql(named_expression, 'alias')} AS {self.sql(named_expression, 'this')}" 2260 for named_expression in expression.args.get("interpolate") or [] 2261 ] 2262 interpolate = ( 2263 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2264 ) 2265 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2266 2267 def cluster_sql(self, expression: exp.Cluster) -> str: 2268 return self.op_expressions("CLUSTER BY", expression) 2269 2270 def distribute_sql(self, expression: exp.Distribute) -> str: 2271 return self.op_expressions("DISTRIBUTE BY", expression) 2272 2273 def sort_sql(self, expression: exp.Sort) -> str: 2274 return self.op_expressions("SORT BY", expression) 2275 2276 def ordered_sql(self, expression: exp.Ordered) -> str: 2277 desc = expression.args.get("desc") 2278 asc = not desc 2279 2280 nulls_first = expression.args.get("nulls_first") 2281 nulls_last = not nulls_first 2282 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2283 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2284 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2285 2286 this = self.sql(expression, "this") 2287 2288 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2289 nulls_sort_change = "" 2290 if nulls_first and ( 2291 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2292 ): 2293 nulls_sort_change = " NULLS FIRST" 2294 elif ( 2295 nulls_last 2296 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2297 and not nulls_are_last 2298 ): 2299 nulls_sort_change = " NULLS LAST" 2300 2301 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2302 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2303 window = expression.find_ancestor(exp.Window, exp.Select) 2304 if isinstance(window, exp.Window) and window.args.get("spec"): 2305 self.unsupported( 2306 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2307 ) 2308 nulls_sort_change = "" 2309 elif self.NULL_ORDERING_SUPPORTED is None: 2310 if expression.this.is_int: 2311 self.unsupported( 2312 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2313 ) 2314 elif not isinstance(expression.this, exp.Rand): 2315 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2316 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2317 nulls_sort_change = "" 2318 2319 with_fill = self.sql(expression, "with_fill") 2320 with_fill = f" {with_fill}" if with_fill else "" 2321 2322 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2323 2324 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2325 window_frame = self.sql(expression, "window_frame") 2326 window_frame = f"{window_frame} " if window_frame else "" 2327 2328 this = self.sql(expression, "this") 2329 2330 return f"{window_frame}{this}" 2331 2332 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2333 partition = self.partition_by_sql(expression) 2334 order = self.sql(expression, "order") 2335 measures = self.expressions(expression, key="measures") 2336 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2337 rows = self.sql(expression, "rows") 2338 rows = self.seg(rows) if rows else "" 2339 after = self.sql(expression, "after") 2340 after = self.seg(after) if after else "" 2341 pattern = self.sql(expression, "pattern") 2342 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2343 definition_sqls = [ 2344 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2345 for definition in expression.args.get("define", []) 2346 ] 2347 definitions = self.expressions(sqls=definition_sqls) 2348 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2349 body = "".join( 2350 ( 2351 partition, 2352 order, 2353 measures, 2354 rows, 2355 after, 2356 pattern, 2357 define, 2358 ) 2359 ) 2360 alias = self.sql(expression, "alias") 2361 alias = f" {alias}" if alias else "" 2362 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2363 2364 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2365 limit = expression.args.get("limit") 2366 2367 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2368 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2369 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2370 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2371 2372 return csv( 2373 *sqls, 2374 *[self.sql(join) for join in expression.args.get("joins") or []], 2375 self.sql(expression, "connect"), 2376 self.sql(expression, "match"), 2377 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2378 self.sql(expression, "prewhere"), 2379 self.sql(expression, "where"), 2380 self.sql(expression, "group"), 2381 self.sql(expression, "having"), 2382 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2383 self.sql(expression, "order"), 2384 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2385 *self.after_limit_modifiers(expression), 2386 self.options_modifier(expression), 2387 sep="", 2388 ) 2389 2390 def options_modifier(self, expression: exp.Expression) -> str: 2391 options = self.expressions(expression, key="options") 2392 return f" {options}" if options else "" 2393 2394 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2395 return "" 2396 2397 def offset_limit_modifiers( 2398 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2399 ) -> t.List[str]: 2400 return [ 2401 self.sql(expression, "offset") if fetch else self.sql(limit), 2402 self.sql(limit) if fetch else self.sql(expression, "offset"), 2403 ] 2404 2405 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2406 locks = self.expressions(expression, key="locks", sep=" ") 2407 locks = f" {locks}" if locks else "" 2408 return [locks, self.sql(expression, "sample")] 2409 2410 def select_sql(self, expression: exp.Select) -> str: 2411 into = expression.args.get("into") 2412 if not self.SUPPORTS_SELECT_INTO and into: 2413 into.pop() 2414 2415 hint = self.sql(expression, "hint") 2416 distinct = self.sql(expression, "distinct") 2417 distinct = f" {distinct}" if distinct else "" 2418 kind = self.sql(expression, "kind") 2419 2420 limit = expression.args.get("limit") 2421 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2422 top = self.limit_sql(limit, top=True) 2423 limit.pop() 2424 else: 2425 top = "" 2426 2427 expressions = self.expressions(expression) 2428 2429 if kind: 2430 if kind in self.SELECT_KINDS: 2431 kind = f" AS {kind}" 2432 else: 2433 if kind == "STRUCT": 2434 expressions = self.expressions( 2435 sqls=[ 2436 self.sql( 2437 exp.Struct( 2438 expressions=[ 2439 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2440 if isinstance(e, exp.Alias) 2441 else e 2442 for e in expression.expressions 2443 ] 2444 ) 2445 ) 2446 ] 2447 ) 2448 kind = "" 2449 2450 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2451 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2452 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2453 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2454 sql = self.query_modifiers( 2455 expression, 2456 f"SELECT{top_distinct}{kind}{expressions}", 2457 self.sql(expression, "into", comment=False), 2458 self.sql(expression, "from", comment=False), 2459 ) 2460 2461 sql = self.prepend_ctes(expression, sql) 2462 2463 if not self.SUPPORTS_SELECT_INTO and into: 2464 if into.args.get("temporary"): 2465 table_kind = " TEMPORARY" 2466 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2467 table_kind = " UNLOGGED" 2468 else: 2469 table_kind = "" 2470 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2471 2472 return sql 2473 2474 def schema_sql(self, expression: exp.Schema) -> str: 2475 this = self.sql(expression, "this") 2476 sql = self.schema_columns_sql(expression) 2477 return f"{this} {sql}" if this and sql else this or sql 2478 2479 def schema_columns_sql(self, expression: exp.Schema) -> str: 2480 if expression.expressions: 2481 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2482 return "" 2483 2484 def star_sql(self, expression: exp.Star) -> str: 2485 except_ = self.expressions(expression, key="except", flat=True) 2486 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2487 replace = self.expressions(expression, key="replace", flat=True) 2488 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2489 rename = self.expressions(expression, key="rename", flat=True) 2490 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2491 return f"*{except_}{replace}{rename}" 2492 2493 def parameter_sql(self, expression: exp.Parameter) -> str: 2494 this = self.sql(expression, "this") 2495 return f"{self.PARAMETER_TOKEN}{this}" 2496 2497 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2498 this = self.sql(expression, "this") 2499 kind = expression.text("kind") 2500 if kind: 2501 kind = f"{kind}." 2502 return f"@@{kind}{this}" 2503 2504 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2505 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2506 2507 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2508 alias = self.sql(expression, "alias") 2509 alias = f"{sep}{alias}" if alias else "" 2510 sample = self.sql(expression, "sample") 2511 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2512 alias = f"{sample}{alias}" 2513 2514 # Set to None so it's not generated again by self.query_modifiers() 2515 expression.set("sample", None) 2516 2517 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2518 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2519 return self.prepend_ctes(expression, sql) 2520 2521 def qualify_sql(self, expression: exp.Qualify) -> str: 2522 this = self.indent(self.sql(expression, "this")) 2523 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2524 2525 def unnest_sql(self, expression: exp.Unnest) -> str: 2526 args = self.expressions(expression, flat=True) 2527 2528 alias = expression.args.get("alias") 2529 offset = expression.args.get("offset") 2530 2531 if self.UNNEST_WITH_ORDINALITY: 2532 if alias and isinstance(offset, exp.Expression): 2533 alias.append("columns", offset) 2534 2535 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2536 columns = alias.columns 2537 alias = self.sql(columns[0]) if columns else "" 2538 else: 2539 alias = self.sql(alias) 2540 2541 alias = f" AS {alias}" if alias else alias 2542 if self.UNNEST_WITH_ORDINALITY: 2543 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2544 else: 2545 if isinstance(offset, exp.Expression): 2546 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2547 elif offset: 2548 suffix = f"{alias} WITH OFFSET" 2549 else: 2550 suffix = alias 2551 2552 return f"UNNEST({args}){suffix}" 2553 2554 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2555 return "" 2556 2557 def where_sql(self, expression: exp.Where) -> str: 2558 this = self.indent(self.sql(expression, "this")) 2559 return f"{self.seg('WHERE')}{self.sep()}{this}" 2560 2561 def window_sql(self, expression: exp.Window) -> str: 2562 this = self.sql(expression, "this") 2563 partition = self.partition_by_sql(expression) 2564 order = expression.args.get("order") 2565 order = self.order_sql(order, flat=True) if order else "" 2566 spec = self.sql(expression, "spec") 2567 alias = self.sql(expression, "alias") 2568 over = self.sql(expression, "over") or "OVER" 2569 2570 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2571 2572 first = expression.args.get("first") 2573 if first is None: 2574 first = "" 2575 else: 2576 first = "FIRST" if first else "LAST" 2577 2578 if not partition and not order and not spec and alias: 2579 return f"{this} {alias}" 2580 2581 args = " ".join(arg for arg in (alias, first, partition, order, spec) if arg) 2582 return f"{this} ({args})" 2583 2584 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2585 partition = self.expressions(expression, key="partition_by", flat=True) 2586 return f"PARTITION BY {partition}" if partition else "" 2587 2588 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2589 kind = self.sql(expression, "kind") 2590 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2591 end = ( 2592 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2593 or "CURRENT ROW" 2594 ) 2595 return f"{kind} BETWEEN {start} AND {end}" 2596 2597 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2598 this = self.sql(expression, "this") 2599 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2600 return f"{this} WITHIN GROUP ({expression_sql})" 2601 2602 def between_sql(self, expression: exp.Between) -> str: 2603 this = self.sql(expression, "this") 2604 low = self.sql(expression, "low") 2605 high = self.sql(expression, "high") 2606 return f"{this} BETWEEN {low} AND {high}" 2607 2608 def bracket_offset_expressions(self, expression: exp.Bracket) -> t.List[exp.Expression]: 2609 return apply_index_offset( 2610 expression.this, 2611 expression.expressions, 2612 self.dialect.INDEX_OFFSET - expression.args.get("offset", 0), 2613 ) 2614 2615 def bracket_sql(self, expression: exp.Bracket) -> str: 2616 expressions = self.bracket_offset_expressions(expression) 2617 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2618 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2619 2620 def all_sql(self, expression: exp.All) -> str: 2621 return f"ALL {self.wrap(expression)}" 2622 2623 def any_sql(self, expression: exp.Any) -> str: 2624 this = self.sql(expression, "this") 2625 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2626 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2627 this = self.wrap(this) 2628 return f"ANY{this}" 2629 return f"ANY {this}" 2630 2631 def exists_sql(self, expression: exp.Exists) -> str: 2632 return f"EXISTS{self.wrap(expression)}" 2633 2634 def case_sql(self, expression: exp.Case) -> str: 2635 this = self.sql(expression, "this") 2636 statements = [f"CASE {this}" if this else "CASE"] 2637 2638 for e in expression.args["ifs"]: 2639 statements.append(f"WHEN {self.sql(e, 'this')}") 2640 statements.append(f"THEN {self.sql(e, 'true')}") 2641 2642 default = self.sql(expression, "default") 2643 2644 if default: 2645 statements.append(f"ELSE {default}") 2646 2647 statements.append("END") 2648 2649 if self.pretty and self.too_wide(statements): 2650 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2651 2652 return " ".join(statements) 2653 2654 def constraint_sql(self, expression: exp.Constraint) -> str: 2655 this = self.sql(expression, "this") 2656 expressions = self.expressions(expression, flat=True) 2657 return f"CONSTRAINT {this} {expressions}" 2658 2659 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2660 order = expression.args.get("order") 2661 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2662 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2663 2664 def extract_sql(self, expression: exp.Extract) -> str: 2665 this = self.sql(expression, "this") if self.EXTRACT_ALLOWS_QUOTES else expression.this.name 2666 expression_sql = self.sql(expression, "expression") 2667 return f"EXTRACT({this} FROM {expression_sql})" 2668 2669 def trim_sql(self, expression: exp.Trim) -> str: 2670 trim_type = self.sql(expression, "position") 2671 2672 if trim_type == "LEADING": 2673 func_name = "LTRIM" 2674 elif trim_type == "TRAILING": 2675 func_name = "RTRIM" 2676 else: 2677 func_name = "TRIM" 2678 2679 return self.func(func_name, expression.this, expression.expression) 2680 2681 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2682 args = expression.expressions 2683 if isinstance(expression, exp.ConcatWs): 2684 args = args[1:] # Skip the delimiter 2685 2686 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2687 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2688 2689 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2690 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2691 2692 return args 2693 2694 def concat_sql(self, expression: exp.Concat) -> str: 2695 expressions = self.convert_concat_args(expression) 2696 2697 # Some dialects don't allow a single-argument CONCAT call 2698 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2699 return self.sql(expressions[0]) 2700 2701 return self.func("CONCAT", *expressions) 2702 2703 def concatws_sql(self, expression: exp.ConcatWs) -> str: 2704 return self.func( 2705 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 2706 ) 2707 2708 def check_sql(self, expression: exp.Check) -> str: 2709 this = self.sql(expression, key="this") 2710 return f"CHECK ({this})" 2711 2712 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2713 expressions = self.expressions(expression, flat=True) 2714 reference = self.sql(expression, "reference") 2715 reference = f" {reference}" if reference else "" 2716 delete = self.sql(expression, "delete") 2717 delete = f" ON DELETE {delete}" if delete else "" 2718 update = self.sql(expression, "update") 2719 update = f" ON UPDATE {update}" if update else "" 2720 return f"FOREIGN KEY ({expressions}){reference}{delete}{update}" 2721 2722 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2723 expressions = self.expressions(expression, flat=True) 2724 options = self.expressions(expression, key="options", flat=True, sep=" ") 2725 options = f" {options}" if options else "" 2726 return f"PRIMARY KEY ({expressions}){options}" 2727 2728 def if_sql(self, expression: exp.If) -> str: 2729 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 2730 2731 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 2732 modifier = expression.args.get("modifier") 2733 modifier = f" {modifier}" if modifier else "" 2734 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 2735 2736 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 2737 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 2738 2739 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 2740 path = self.expressions(expression, sep="", flat=True).lstrip(".") 2741 if self.QUOTE_JSON_PATH: 2742 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 2743 2744 return path 2745 2746 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 2747 if isinstance(expression, exp.JSONPathPart): 2748 transform = self.TRANSFORMS.get(expression.__class__) 2749 if not callable(transform): 2750 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 2751 return "" 2752 2753 return transform(self, expression) 2754 2755 if isinstance(expression, int): 2756 return str(expression) 2757 2758 if self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 2759 escaped = expression.replace("'", "\\'") 2760 escaped = f"\\'{expression}\\'" 2761 else: 2762 escaped = expression.replace('"', '\\"') 2763 escaped = f'"{escaped}"' 2764 2765 return escaped 2766 2767 def formatjson_sql(self, expression: exp.FormatJson) -> str: 2768 return f"{self.sql(expression, 'this')} FORMAT JSON" 2769 2770 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 2771 null_handling = expression.args.get("null_handling") 2772 null_handling = f" {null_handling}" if null_handling else "" 2773 2774 unique_keys = expression.args.get("unique_keys") 2775 if unique_keys is not None: 2776 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 2777 else: 2778 unique_keys = "" 2779 2780 return_type = self.sql(expression, "return_type") 2781 return_type = f" RETURNING {return_type}" if return_type else "" 2782 encoding = self.sql(expression, "encoding") 2783 encoding = f" ENCODING {encoding}" if encoding else "" 2784 2785 return self.func( 2786 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 2787 *expression.expressions, 2788 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 2789 ) 2790 2791 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 2792 return self.jsonobject_sql(expression) 2793 2794 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 2795 null_handling = expression.args.get("null_handling") 2796 null_handling = f" {null_handling}" if null_handling else "" 2797 return_type = self.sql(expression, "return_type") 2798 return_type = f" RETURNING {return_type}" if return_type else "" 2799 strict = " STRICT" if expression.args.get("strict") else "" 2800 return self.func( 2801 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 2802 ) 2803 2804 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 2805 this = self.sql(expression, "this") 2806 order = self.sql(expression, "order") 2807 null_handling = expression.args.get("null_handling") 2808 null_handling = f" {null_handling}" if null_handling else "" 2809 return_type = self.sql(expression, "return_type") 2810 return_type = f" RETURNING {return_type}" if return_type else "" 2811 strict = " STRICT" if expression.args.get("strict") else "" 2812 return self.func( 2813 "JSON_ARRAYAGG", 2814 this, 2815 suffix=f"{order}{null_handling}{return_type}{strict})", 2816 ) 2817 2818 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 2819 path = self.sql(expression, "path") 2820 path = f" PATH {path}" if path else "" 2821 nested_schema = self.sql(expression, "nested_schema") 2822 2823 if nested_schema: 2824 return f"NESTED{path} {nested_schema}" 2825 2826 this = self.sql(expression, "this") 2827 kind = self.sql(expression, "kind") 2828 kind = f" {kind}" if kind else "" 2829 return f"{this}{kind}{path}" 2830 2831 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 2832 return self.func("COLUMNS", *expression.expressions) 2833 2834 def jsontable_sql(self, expression: exp.JSONTable) -> str: 2835 this = self.sql(expression, "this") 2836 path = self.sql(expression, "path") 2837 path = f", {path}" if path else "" 2838 error_handling = expression.args.get("error_handling") 2839 error_handling = f" {error_handling}" if error_handling else "" 2840 empty_handling = expression.args.get("empty_handling") 2841 empty_handling = f" {empty_handling}" if empty_handling else "" 2842 schema = self.sql(expression, "schema") 2843 return self.func( 2844 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 2845 ) 2846 2847 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 2848 this = self.sql(expression, "this") 2849 kind = self.sql(expression, "kind") 2850 path = self.sql(expression, "path") 2851 path = f" {path}" if path else "" 2852 as_json = " AS JSON" if expression.args.get("as_json") else "" 2853 return f"{this} {kind}{path}{as_json}" 2854 2855 def openjson_sql(self, expression: exp.OpenJSON) -> str: 2856 this = self.sql(expression, "this") 2857 path = self.sql(expression, "path") 2858 path = f", {path}" if path else "" 2859 expressions = self.expressions(expression) 2860 with_ = ( 2861 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 2862 if expressions 2863 else "" 2864 ) 2865 return f"OPENJSON({this}{path}){with_}" 2866 2867 def in_sql(self, expression: exp.In) -> str: 2868 query = expression.args.get("query") 2869 unnest = expression.args.get("unnest") 2870 field = expression.args.get("field") 2871 is_global = " GLOBAL" if expression.args.get("is_global") else "" 2872 2873 if query: 2874 in_sql = self.sql(query) 2875 elif unnest: 2876 in_sql = self.in_unnest_op(unnest) 2877 elif field: 2878 in_sql = self.sql(field) 2879 else: 2880 in_sql = f"({self.expressions(expression, flat=True)})" 2881 2882 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 2883 2884 def in_unnest_op(self, unnest: exp.Unnest) -> str: 2885 return f"(SELECT {self.sql(unnest)})" 2886 2887 def interval_sql(self, expression: exp.Interval) -> str: 2888 unit = self.sql(expression, "unit") 2889 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 2890 unit = self.TIME_PART_SINGULARS.get(unit, unit) 2891 unit = f" {unit}" if unit else "" 2892 2893 if self.SINGLE_STRING_INTERVAL: 2894 this = expression.this.name if expression.this else "" 2895 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 2896 2897 this = self.sql(expression, "this") 2898 if this: 2899 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 2900 this = f" {this}" if unwrapped else f" ({this})" 2901 2902 return f"INTERVAL{this}{unit}" 2903 2904 def return_sql(self, expression: exp.Return) -> str: 2905 return f"RETURN {self.sql(expression, 'this')}" 2906 2907 def reference_sql(self, expression: exp.Reference) -> str: 2908 this = self.sql(expression, "this") 2909 expressions = self.expressions(expression, flat=True) 2910 expressions = f"({expressions})" if expressions else "" 2911 options = self.expressions(expression, key="options", flat=True, sep=" ") 2912 options = f" {options}" if options else "" 2913 return f"REFERENCES {this}{expressions}{options}" 2914 2915 def anonymous_sql(self, expression: exp.Anonymous) -> str: 2916 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 2917 parent = expression.parent 2918 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 2919 return self.func( 2920 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 2921 ) 2922 2923 def paren_sql(self, expression: exp.Paren) -> str: 2924 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 2925 return f"({sql}{self.seg(')', sep='')}" 2926 2927 def neg_sql(self, expression: exp.Neg) -> str: 2928 # This makes sure we don't convert "- - 5" to "--5", which is a comment 2929 this_sql = self.sql(expression, "this") 2930 sep = " " if this_sql[0] == "-" else "" 2931 return f"-{sep}{this_sql}" 2932 2933 def not_sql(self, expression: exp.Not) -> str: 2934 return f"NOT {self.sql(expression, 'this')}" 2935 2936 def alias_sql(self, expression: exp.Alias) -> str: 2937 alias = self.sql(expression, "alias") 2938 alias = f" AS {alias}" if alias else "" 2939 return f"{self.sql(expression, 'this')}{alias}" 2940 2941 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 2942 alias = expression.args["alias"] 2943 2944 identifier_alias = isinstance(alias, exp.Identifier) 2945 literal_alias = isinstance(alias, exp.Literal) 2946 2947 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 2948 alias.replace(exp.Literal.string(alias.output_name)) 2949 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 2950 alias.replace(exp.to_identifier(alias.output_name)) 2951 2952 return self.alias_sql(expression) 2953 2954 def aliases_sql(self, expression: exp.Aliases) -> str: 2955 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 2956 2957 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 2958 this = self.sql(expression, "this") 2959 index = self.sql(expression, "expression") 2960 return f"{this} AT {index}" 2961 2962 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 2963 this = self.sql(expression, "this") 2964 zone = self.sql(expression, "zone") 2965 return f"{this} AT TIME ZONE {zone}" 2966 2967 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 2968 this = self.sql(expression, "this") 2969 zone = self.sql(expression, "zone") 2970 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 2971 2972 def add_sql(self, expression: exp.Add) -> str: 2973 return self.binary(expression, "+") 2974 2975 def and_sql( 2976 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 2977 ) -> str: 2978 return self.connector_sql(expression, "AND", stack) 2979 2980 def or_sql( 2981 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 2982 ) -> str: 2983 return self.connector_sql(expression, "OR", stack) 2984 2985 def xor_sql( 2986 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 2987 ) -> str: 2988 return self.connector_sql(expression, "XOR", stack) 2989 2990 def connector_sql( 2991 self, 2992 expression: exp.Connector, 2993 op: str, 2994 stack: t.Optional[t.List[str | exp.Expression]] = None, 2995 ) -> str: 2996 if stack is not None: 2997 if expression.expressions: 2998 stack.append(self.expressions(expression, sep=f" {op} ")) 2999 else: 3000 stack.append(expression.right) 3001 if expression.comments and self.comments: 3002 for comment in expression.comments: 3003 if comment: 3004 op += f" /*{self.pad_comment(comment)}*/" 3005 stack.extend((op, expression.left)) 3006 return op 3007 3008 stack = [expression] 3009 sqls: t.List[str] = [] 3010 ops = set() 3011 3012 while stack: 3013 node = stack.pop() 3014 if isinstance(node, exp.Connector): 3015 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3016 else: 3017 sql = self.sql(node) 3018 if sqls and sqls[-1] in ops: 3019 sqls[-1] += f" {sql}" 3020 else: 3021 sqls.append(sql) 3022 3023 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3024 return sep.join(sqls) 3025 3026 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3027 return self.binary(expression, "&") 3028 3029 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3030 return self.binary(expression, "<<") 3031 3032 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3033 return f"~{self.sql(expression, 'this')}" 3034 3035 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3036 return self.binary(expression, "|") 3037 3038 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3039 return self.binary(expression, ">>") 3040 3041 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3042 return self.binary(expression, "^") 3043 3044 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3045 format_sql = self.sql(expression, "format") 3046 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3047 to_sql = self.sql(expression, "to") 3048 to_sql = f" {to_sql}" if to_sql else "" 3049 action = self.sql(expression, "action") 3050 action = f" {action}" if action else "" 3051 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{format_sql}{action})" 3052 3053 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3054 zone = self.sql(expression, "this") 3055 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3056 3057 def collate_sql(self, expression: exp.Collate) -> str: 3058 if self.COLLATE_IS_FUNC: 3059 return self.function_fallback_sql(expression) 3060 return self.binary(expression, "COLLATE") 3061 3062 def command_sql(self, expression: exp.Command) -> str: 3063 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3064 3065 def comment_sql(self, expression: exp.Comment) -> str: 3066 this = self.sql(expression, "this") 3067 kind = expression.args["kind"] 3068 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3069 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3070 expression_sql = self.sql(expression, "expression") 3071 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3072 3073 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3074 this = self.sql(expression, "this") 3075 delete = " DELETE" if expression.args.get("delete") else "" 3076 recompress = self.sql(expression, "recompress") 3077 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3078 to_disk = self.sql(expression, "to_disk") 3079 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3080 to_volume = self.sql(expression, "to_volume") 3081 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3082 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3083 3084 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3085 where = self.sql(expression, "where") 3086 group = self.sql(expression, "group") 3087 aggregates = self.expressions(expression, key="aggregates") 3088 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3089 3090 if not (where or group or aggregates) and len(expression.expressions) == 1: 3091 return f"TTL {self.expressions(expression, flat=True)}" 3092 3093 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3094 3095 def transaction_sql(self, expression: exp.Transaction) -> str: 3096 return "BEGIN" 3097 3098 def commit_sql(self, expression: exp.Commit) -> str: 3099 chain = expression.args.get("chain") 3100 if chain is not None: 3101 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3102 3103 return f"COMMIT{chain or ''}" 3104 3105 def rollback_sql(self, expression: exp.Rollback) -> str: 3106 savepoint = expression.args.get("savepoint") 3107 savepoint = f" TO {savepoint}" if savepoint else "" 3108 return f"ROLLBACK{savepoint}" 3109 3110 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3111 this = self.sql(expression, "this") 3112 3113 dtype = self.sql(expression, "dtype") 3114 if dtype: 3115 collate = self.sql(expression, "collate") 3116 collate = f" COLLATE {collate}" if collate else "" 3117 using = self.sql(expression, "using") 3118 using = f" USING {using}" if using else "" 3119 return f"ALTER COLUMN {this} SET DATA TYPE {dtype}{collate}{using}" 3120 3121 default = self.sql(expression, "default") 3122 if default: 3123 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3124 3125 comment = self.sql(expression, "comment") 3126 if comment: 3127 return f"ALTER COLUMN {this} COMMENT {comment}" 3128 3129 allow_null = expression.args.get("allow_null") 3130 drop = expression.args.get("drop") 3131 3132 if not drop and not allow_null: 3133 self.unsupported("Unsupported ALTER COLUMN syntax") 3134 3135 if allow_null is not None: 3136 keyword = "DROP" if drop else "SET" 3137 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3138 3139 return f"ALTER COLUMN {this} DROP DEFAULT" 3140 3141 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3142 this = self.sql(expression, "this") 3143 if not isinstance(expression.this, exp.Var): 3144 this = f"KEY DISTKEY {this}" 3145 return f"ALTER DISTSTYLE {this}" 3146 3147 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3148 compound = " COMPOUND" if expression.args.get("compound") else "" 3149 this = self.sql(expression, "this") 3150 expressions = self.expressions(expression, flat=True) 3151 expressions = f"({expressions})" if expressions else "" 3152 return f"ALTER{compound} SORTKEY {this or expressions}" 3153 3154 def renametable_sql(self, expression: exp.RenameTable) -> str: 3155 if not self.RENAME_TABLE_WITH_DB: 3156 # Remove db from tables 3157 expression = expression.transform( 3158 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3159 ).assert_is(exp.RenameTable) 3160 this = self.sql(expression, "this") 3161 return f"RENAME TO {this}" 3162 3163 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3164 exists = " IF EXISTS" if expression.args.get("exists") else "" 3165 old_column = self.sql(expression, "this") 3166 new_column = self.sql(expression, "to") 3167 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3168 3169 def alterset_sql(self, expression: exp.AlterSet) -> str: 3170 exprs = self.expressions(expression, flat=True) 3171 return f"SET {exprs}" 3172 3173 def alter_sql(self, expression: exp.Alter) -> str: 3174 actions = expression.args["actions"] 3175 3176 if isinstance(actions[0], exp.ColumnDef): 3177 actions = self.add_column_sql(expression) 3178 elif isinstance(actions[0], exp.Schema): 3179 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ") 3180 elif isinstance(actions[0], exp.Delete): 3181 actions = self.expressions(expression, key="actions", flat=True) 3182 elif isinstance(actions[0], exp.Query): 3183 actions = "AS " + self.expressions(expression, key="actions") 3184 else: 3185 actions = self.expressions(expression, key="actions", flat=True) 3186 3187 exists = " IF EXISTS" if expression.args.get("exists") else "" 3188 on_cluster = self.sql(expression, "cluster") 3189 on_cluster = f" {on_cluster}" if on_cluster else "" 3190 only = " ONLY" if expression.args.get("only") else "" 3191 options = self.expressions(expression, key="options") 3192 options = f", {options}" if options else "" 3193 kind = self.sql(expression, "kind") 3194 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster} {actions}{options}" 3195 3196 def add_column_sql(self, expression: exp.Alter) -> str: 3197 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3198 return self.expressions( 3199 expression, 3200 key="actions", 3201 prefix="ADD COLUMN ", 3202 skip_first=True, 3203 ) 3204 return f"ADD {self.expressions(expression, key='actions', flat=True)}" 3205 3206 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3207 expressions = self.expressions(expression) 3208 exists = " IF EXISTS " if expression.args.get("exists") else " " 3209 return f"DROP{exists}{expressions}" 3210 3211 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3212 return f"ADD {self.expressions(expression)}" 3213 3214 def distinct_sql(self, expression: exp.Distinct) -> str: 3215 this = self.expressions(expression, flat=True) 3216 3217 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3218 case = exp.case() 3219 for arg in expression.expressions: 3220 case = case.when(arg.is_(exp.null()), exp.null()) 3221 this = self.sql(case.else_(f"({this})")) 3222 3223 this = f" {this}" if this else "" 3224 3225 on = self.sql(expression, "on") 3226 on = f" ON {on}" if on else "" 3227 return f"DISTINCT{this}{on}" 3228 3229 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3230 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3231 3232 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3233 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3234 3235 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3236 this_sql = self.sql(expression, "this") 3237 expression_sql = self.sql(expression, "expression") 3238 kind = "MAX" if expression.args.get("max") else "MIN" 3239 return f"{this_sql} HAVING {kind} {expression_sql}" 3240 3241 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3242 return self.sql( 3243 exp.Cast( 3244 this=exp.Div(this=expression.this, expression=expression.expression), 3245 to=exp.DataType(this=exp.DataType.Type.INT), 3246 ) 3247 ) 3248 3249 def dpipe_sql(self, expression: exp.DPipe) -> str: 3250 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3251 return self.func( 3252 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3253 ) 3254 return self.binary(expression, "||") 3255 3256 def div_sql(self, expression: exp.Div) -> str: 3257 l, r = expression.left, expression.right 3258 3259 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3260 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3261 3262 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3263 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3264 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3265 3266 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3267 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3268 return self.sql( 3269 exp.cast( 3270 l / r, 3271 to=exp.DataType.Type.BIGINT, 3272 ) 3273 ) 3274 3275 return self.binary(expression, "/") 3276 3277 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3278 return self.binary(expression, "OVERLAPS") 3279 3280 def distance_sql(self, expression: exp.Distance) -> str: 3281 return self.binary(expression, "<->") 3282 3283 def dot_sql(self, expression: exp.Dot) -> str: 3284 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3285 3286 def eq_sql(self, expression: exp.EQ) -> str: 3287 return self.binary(expression, "=") 3288 3289 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3290 return self.binary(expression, ":=") 3291 3292 def escape_sql(self, expression: exp.Escape) -> str: 3293 return self.binary(expression, "ESCAPE") 3294 3295 def glob_sql(self, expression: exp.Glob) -> str: 3296 return self.binary(expression, "GLOB") 3297 3298 def gt_sql(self, expression: exp.GT) -> str: 3299 return self.binary(expression, ">") 3300 3301 def gte_sql(self, expression: exp.GTE) -> str: 3302 return self.binary(expression, ">=") 3303 3304 def ilike_sql(self, expression: exp.ILike) -> str: 3305 return self.binary(expression, "ILIKE") 3306 3307 def ilikeany_sql(self, expression: exp.ILikeAny) -> str: 3308 return self.binary(expression, "ILIKE ANY") 3309 3310 def is_sql(self, expression: exp.Is) -> str: 3311 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3312 return self.sql( 3313 expression.this if expression.expression.this else exp.not_(expression.this) 3314 ) 3315 return self.binary(expression, "IS") 3316 3317 def like_sql(self, expression: exp.Like) -> str: 3318 return self.binary(expression, "LIKE") 3319 3320 def likeany_sql(self, expression: exp.LikeAny) -> str: 3321 return self.binary(expression, "LIKE ANY") 3322 3323 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3324 return self.binary(expression, "SIMILAR TO") 3325 3326 def lt_sql(self, expression: exp.LT) -> str: 3327 return self.binary(expression, "<") 3328 3329 def lte_sql(self, expression: exp.LTE) -> str: 3330 return self.binary(expression, "<=") 3331 3332 def mod_sql(self, expression: exp.Mod) -> str: 3333 return self.binary(expression, "%") 3334 3335 def mul_sql(self, expression: exp.Mul) -> str: 3336 return self.binary(expression, "*") 3337 3338 def neq_sql(self, expression: exp.NEQ) -> str: 3339 return self.binary(expression, "<>") 3340 3341 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3342 return self.binary(expression, "IS NOT DISTINCT FROM") 3343 3344 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3345 return self.binary(expression, "IS DISTINCT FROM") 3346 3347 def slice_sql(self, expression: exp.Slice) -> str: 3348 return self.binary(expression, ":") 3349 3350 def sub_sql(self, expression: exp.Sub) -> str: 3351 return self.binary(expression, "-") 3352 3353 def trycast_sql(self, expression: exp.TryCast) -> str: 3354 return self.cast_sql(expression, safe_prefix="TRY_") 3355 3356 def try_sql(self, expression: exp.Try) -> str: 3357 if not self.TRY_SUPPORTED: 3358 self.unsupported("Unsupported TRY function") 3359 return self.sql(expression, "this") 3360 3361 return self.func("TRY", expression.this) 3362 3363 def log_sql(self, expression: exp.Log) -> str: 3364 this = expression.this 3365 expr = expression.expression 3366 3367 if self.dialect.LOG_BASE_FIRST is False: 3368 this, expr = expr, this 3369 elif self.dialect.LOG_BASE_FIRST is None and expr: 3370 if this.name in ("2", "10"): 3371 return self.func(f"LOG{this.name}", expr) 3372 3373 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3374 3375 return self.func("LOG", this, expr) 3376 3377 def use_sql(self, expression: exp.Use) -> str: 3378 kind = self.sql(expression, "kind") 3379 kind = f" {kind}" if kind else "" 3380 this = self.sql(expression, "this") 3381 this = f" {this}" if this else "" 3382 return f"USE{kind}{this}" 3383 3384 def binary(self, expression: exp.Binary, op: str) -> str: 3385 sqls: t.List[str] = [] 3386 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3387 binary_type = type(expression) 3388 3389 while stack: 3390 node = stack.pop() 3391 3392 if type(node) is binary_type: 3393 op_func = node.args.get("operator") 3394 if op_func: 3395 op = f"OPERATOR({self.sql(op_func)})" 3396 3397 stack.append(node.right) 3398 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3399 stack.append(node.left) 3400 else: 3401 sqls.append(self.sql(node)) 3402 3403 return "".join(sqls) 3404 3405 def function_fallback_sql(self, expression: exp.Func) -> str: 3406 args = [] 3407 3408 for key in expression.arg_types: 3409 arg_value = expression.args.get(key) 3410 3411 if isinstance(arg_value, list): 3412 for value in arg_value: 3413 args.append(value) 3414 elif arg_value is not None: 3415 args.append(arg_value) 3416 3417 if self.normalize_functions: 3418 name = expression.sql_name() 3419 else: 3420 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3421 3422 return self.func(name, *args) 3423 3424 def func( 3425 self, 3426 name: str, 3427 *args: t.Optional[exp.Expression | str], 3428 prefix: str = "(", 3429 suffix: str = ")", 3430 normalize: bool = True, 3431 ) -> str: 3432 name = self.normalize_func(name) if normalize else name 3433 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3434 3435 def format_args(self, *args: t.Optional[str | exp.Expression]) -> str: 3436 arg_sqls = tuple( 3437 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3438 ) 3439 if self.pretty and self.too_wide(arg_sqls): 3440 return self.indent("\n" + ",\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True) 3441 return ", ".join(arg_sqls) 3442 3443 def too_wide(self, args: t.Iterable) -> bool: 3444 return sum(len(arg) for arg in args) > self.max_text_width 3445 3446 def format_time( 3447 self, 3448 expression: exp.Expression, 3449 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3450 inverse_time_trie: t.Optional[t.Dict] = None, 3451 ) -> t.Optional[str]: 3452 return format_time( 3453 self.sql(expression, "format"), 3454 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3455 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3456 ) 3457 3458 def expressions( 3459 self, 3460 expression: t.Optional[exp.Expression] = None, 3461 key: t.Optional[str] = None, 3462 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3463 flat: bool = False, 3464 indent: bool = True, 3465 skip_first: bool = False, 3466 skip_last: bool = False, 3467 sep: str = ", ", 3468 prefix: str = "", 3469 dynamic: bool = False, 3470 new_line: bool = False, 3471 ) -> str: 3472 expressions = expression.args.get(key or "expressions") if expression else sqls 3473 3474 if not expressions: 3475 return "" 3476 3477 if flat: 3478 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3479 3480 num_sqls = len(expressions) 3481 result_sqls = [] 3482 3483 for i, e in enumerate(expressions): 3484 sql = self.sql(e, comment=False) 3485 if not sql: 3486 continue 3487 3488 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3489 3490 if self.pretty: 3491 if self.leading_comma: 3492 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3493 else: 3494 result_sqls.append( 3495 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3496 ) 3497 else: 3498 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3499 3500 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3501 if new_line: 3502 result_sqls.insert(0, "") 3503 result_sqls.append("") 3504 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3505 else: 3506 result_sql = "".join(result_sqls) 3507 3508 return ( 3509 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3510 if indent 3511 else result_sql 3512 ) 3513 3514 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3515 flat = flat or isinstance(expression.parent, exp.Properties) 3516 expressions_sql = self.expressions(expression, flat=flat) 3517 if flat: 3518 return f"{op} {expressions_sql}" 3519 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3520 3521 def naked_property(self, expression: exp.Property) -> str: 3522 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3523 if not property_name: 3524 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3525 return f"{property_name} {self.sql(expression, 'this')}" 3526 3527 def tag_sql(self, expression: exp.Tag) -> str: 3528 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3529 3530 def token_sql(self, token_type: TokenType) -> str: 3531 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3532 3533 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3534 this = self.sql(expression, "this") 3535 expressions = self.no_identify(self.expressions, expression) 3536 expressions = ( 3537 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3538 ) 3539 return f"{this}{expressions}" 3540 3541 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3542 this = self.sql(expression, "this") 3543 expressions = self.expressions(expression, flat=True) 3544 return f"{this}({expressions})" 3545 3546 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3547 return self.binary(expression, "=>") 3548 3549 def when_sql(self, expression: exp.When) -> str: 3550 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3551 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3552 condition = self.sql(expression, "condition") 3553 condition = f" AND {condition}" if condition else "" 3554 3555 then_expression = expression.args.get("then") 3556 if isinstance(then_expression, exp.Insert): 3557 this = self.sql(then_expression, "this") 3558 this = f"INSERT {this}" if this else "INSERT" 3559 then = self.sql(then_expression, "expression") 3560 then = f"{this} VALUES {then}" if then else this 3561 elif isinstance(then_expression, exp.Update): 3562 if isinstance(then_expression.args.get("expressions"), exp.Star): 3563 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3564 else: 3565 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}" 3566 else: 3567 then = self.sql(then_expression) 3568 return f"WHEN {matched}{source}{condition} THEN {then}" 3569 3570 def merge_sql(self, expression: exp.Merge) -> str: 3571 table = expression.this 3572 table_alias = "" 3573 3574 hints = table.args.get("hints") 3575 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3576 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3577 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3578 3579 this = self.sql(table) 3580 using = f"USING {self.sql(expression, 'using')}" 3581 on = f"ON {self.sql(expression, 'on')}" 3582 expressions = self.expressions(expression, sep=" ", indent=False) 3583 sep = self.sep() 3584 3585 return self.prepend_ctes( 3586 expression, f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{expressions}" 3587 ) 3588 3589 def tochar_sql(self, expression: exp.ToChar) -> str: 3590 if expression.args.get("format"): 3591 self.unsupported("Format argument unsupported for TO_CHAR/TO_VARCHAR function") 3592 3593 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3594 3595 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3596 if not self.SUPPORTS_TO_NUMBER: 3597 self.unsupported("Unsupported TO_NUMBER function") 3598 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3599 3600 fmt = expression.args.get("format") 3601 if not fmt: 3602 self.unsupported("Conversion format is required for TO_NUMBER") 3603 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3604 3605 return self.func("TO_NUMBER", expression.this, fmt) 3606 3607 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3608 this = self.sql(expression, "this") 3609 kind = self.sql(expression, "kind") 3610 settings_sql = self.expressions(expression, key="settings", sep=" ") 3611 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3612 return f"{this}({kind}{args})" 3613 3614 def dictrange_sql(self, expression: exp.DictRange) -> str: 3615 this = self.sql(expression, "this") 3616 max = self.sql(expression, "max") 3617 min = self.sql(expression, "min") 3618 return f"{this}(MIN {min} MAX {max})" 3619 3620 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 3621 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 3622 3623 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 3624 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 3625 3626 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 3627 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3628 expressions = self.expressions(expression, flat=True) 3629 expressions = f" {self.wrap(expressions)}" if expressions else "" 3630 buckets = self.sql(expression, "buckets") 3631 kind = self.sql(expression, "kind") 3632 buckets = f" BUCKETS {buckets}" if buckets else "" 3633 order = self.sql(expression, "order") 3634 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 3635 3636 def oncluster_sql(self, expression: exp.OnCluster) -> str: 3637 return "" 3638 3639 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 3640 expressions = self.expressions(expression, key="expressions", flat=True) 3641 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 3642 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 3643 buckets = self.sql(expression, "buckets") 3644 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 3645 3646 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 3647 this = self.sql(expression, "this") 3648 having = self.sql(expression, "having") 3649 3650 if having: 3651 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 3652 3653 return self.func("ANY_VALUE", this) 3654 3655 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 3656 transform = self.func("TRANSFORM", *expression.expressions) 3657 row_format_before = self.sql(expression, "row_format_before") 3658 row_format_before = f" {row_format_before}" if row_format_before else "" 3659 record_writer = self.sql(expression, "record_writer") 3660 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 3661 using = f" USING {self.sql(expression, 'command_script')}" 3662 schema = self.sql(expression, "schema") 3663 schema = f" AS {schema}" if schema else "" 3664 row_format_after = self.sql(expression, "row_format_after") 3665 row_format_after = f" {row_format_after}" if row_format_after else "" 3666 record_reader = self.sql(expression, "record_reader") 3667 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 3668 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 3669 3670 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 3671 key_block_size = self.sql(expression, "key_block_size") 3672 if key_block_size: 3673 return f"KEY_BLOCK_SIZE = {key_block_size}" 3674 3675 using = self.sql(expression, "using") 3676 if using: 3677 return f"USING {using}" 3678 3679 parser = self.sql(expression, "parser") 3680 if parser: 3681 return f"WITH PARSER {parser}" 3682 3683 comment = self.sql(expression, "comment") 3684 if comment: 3685 return f"COMMENT {comment}" 3686 3687 visible = expression.args.get("visible") 3688 if visible is not None: 3689 return "VISIBLE" if visible else "INVISIBLE" 3690 3691 engine_attr = self.sql(expression, "engine_attr") 3692 if engine_attr: 3693 return f"ENGINE_ATTRIBUTE = {engine_attr}" 3694 3695 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 3696 if secondary_engine_attr: 3697 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 3698 3699 self.unsupported("Unsupported index constraint option.") 3700 return "" 3701 3702 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 3703 enforced = " ENFORCED" if expression.args.get("enforced") else "" 3704 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 3705 3706 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 3707 kind = self.sql(expression, "kind") 3708 kind = f"{kind} INDEX" if kind else "INDEX" 3709 this = self.sql(expression, "this") 3710 this = f" {this}" if this else "" 3711 index_type = self.sql(expression, "index_type") 3712 index_type = f" USING {index_type}" if index_type else "" 3713 expressions = self.expressions(expression, flat=True) 3714 expressions = f" ({expressions})" if expressions else "" 3715 options = self.expressions(expression, key="options", sep=" ") 3716 options = f" {options}" if options else "" 3717 return f"{kind}{this}{index_type}{expressions}{options}" 3718 3719 def nvl2_sql(self, expression: exp.Nvl2) -> str: 3720 if self.NVL2_SUPPORTED: 3721 return self.function_fallback_sql(expression) 3722 3723 case = exp.Case().when( 3724 expression.this.is_(exp.null()).not_(copy=False), 3725 expression.args["true"], 3726 copy=False, 3727 ) 3728 else_cond = expression.args.get("false") 3729 if else_cond: 3730 case.else_(else_cond, copy=False) 3731 3732 return self.sql(case) 3733 3734 def comprehension_sql(self, expression: exp.Comprehension) -> str: 3735 this = self.sql(expression, "this") 3736 expr = self.sql(expression, "expression") 3737 iterator = self.sql(expression, "iterator") 3738 condition = self.sql(expression, "condition") 3739 condition = f" IF {condition}" if condition else "" 3740 return f"{this} FOR {expr} IN {iterator}{condition}" 3741 3742 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 3743 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 3744 3745 def opclass_sql(self, expression: exp.Opclass) -> str: 3746 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 3747 3748 def predict_sql(self, expression: exp.Predict) -> str: 3749 model = self.sql(expression, "this") 3750 model = f"MODEL {model}" 3751 table = self.sql(expression, "expression") 3752 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 3753 parameters = self.sql(expression, "params_struct") 3754 return self.func("PREDICT", model, table, parameters or None) 3755 3756 def forin_sql(self, expression: exp.ForIn) -> str: 3757 this = self.sql(expression, "this") 3758 expression_sql = self.sql(expression, "expression") 3759 return f"FOR {this} DO {expression_sql}" 3760 3761 def refresh_sql(self, expression: exp.Refresh) -> str: 3762 this = self.sql(expression, "this") 3763 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 3764 return f"REFRESH {table}{this}" 3765 3766 def toarray_sql(self, expression: exp.ToArray) -> str: 3767 arg = expression.this 3768 if not arg.type: 3769 from sqlglot.optimizer.annotate_types import annotate_types 3770 3771 arg = annotate_types(arg) 3772 3773 if arg.is_type(exp.DataType.Type.ARRAY): 3774 return self.sql(arg) 3775 3776 cond_for_null = arg.is_(exp.null()) 3777 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 3778 3779 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 3780 this = expression.this 3781 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 3782 return self.sql(this) 3783 3784 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 3785 3786 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 3787 this = expression.this 3788 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 3789 return self.sql(this) 3790 3791 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP)) 3792 3793 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 3794 this = expression.this 3795 time_format = self.format_time(expression) 3796 3797 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 3798 return self.sql( 3799 exp.cast( 3800 exp.StrToTime(this=this, format=expression.args["format"]), 3801 exp.DataType.Type.DATE, 3802 ) 3803 ) 3804 3805 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 3806 return self.sql(this) 3807 3808 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 3809 3810 def unixdate_sql(self, expression: exp.UnixDate) -> str: 3811 return self.sql( 3812 exp.func( 3813 "DATEDIFF", 3814 expression.this, 3815 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 3816 "day", 3817 ) 3818 ) 3819 3820 def lastday_sql(self, expression: exp.LastDay) -> str: 3821 if self.LAST_DAY_SUPPORTS_DATE_PART: 3822 return self.function_fallback_sql(expression) 3823 3824 unit = expression.text("unit") 3825 if unit and unit != "MONTH": 3826 self.unsupported("Date parts are not supported in LAST_DAY.") 3827 3828 return self.func("LAST_DAY", expression.this) 3829 3830 def dateadd_sql(self, expression: exp.DateAdd) -> str: 3831 from sqlglot.dialects.dialect import unit_to_str 3832 3833 return self.func( 3834 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 3835 ) 3836 3837 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 3838 if self.CAN_IMPLEMENT_ARRAY_ANY: 3839 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 3840 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 3841 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 3842 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 3843 3844 from sqlglot.dialects import Dialect 3845 3846 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 3847 if self.dialect.__class__ != Dialect: 3848 self.unsupported("ARRAY_ANY is unsupported") 3849 3850 return self.function_fallback_sql(expression) 3851 3852 def struct_sql(self, expression: exp.Struct) -> str: 3853 expression.set( 3854 "expressions", 3855 [ 3856 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 3857 if isinstance(e, exp.PropertyEQ) 3858 else e 3859 for e in expression.expressions 3860 ], 3861 ) 3862 3863 return self.function_fallback_sql(expression) 3864 3865 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 3866 low = self.sql(expression, "this") 3867 high = self.sql(expression, "expression") 3868 3869 return f"{low} TO {high}" 3870 3871 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 3872 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 3873 tables = f" {self.expressions(expression)}" 3874 3875 exists = " IF EXISTS" if expression.args.get("exists") else "" 3876 3877 on_cluster = self.sql(expression, "cluster") 3878 on_cluster = f" {on_cluster}" if on_cluster else "" 3879 3880 identity = self.sql(expression, "identity") 3881 identity = f" {identity} IDENTITY" if identity else "" 3882 3883 option = self.sql(expression, "option") 3884 option = f" {option}" if option else "" 3885 3886 partition = self.sql(expression, "partition") 3887 partition = f" {partition}" if partition else "" 3888 3889 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 3890 3891 # This transpiles T-SQL's CONVERT function 3892 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 3893 def convert_sql(self, expression: exp.Convert) -> str: 3894 to = expression.this 3895 value = expression.expression 3896 style = expression.args.get("style") 3897 safe = expression.args.get("safe") 3898 strict = expression.args.get("strict") 3899 3900 if not to or not value: 3901 return "" 3902 3903 # Retrieve length of datatype and override to default if not specified 3904 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 3905 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 3906 3907 transformed: t.Optional[exp.Expression] = None 3908 cast = exp.Cast if strict else exp.TryCast 3909 3910 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 3911 if isinstance(style, exp.Literal) and style.is_int: 3912 from sqlglot.dialects.tsql import TSQL 3913 3914 style_value = style.name 3915 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 3916 if not converted_style: 3917 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 3918 3919 fmt = exp.Literal.string(converted_style) 3920 3921 if to.this == exp.DataType.Type.DATE: 3922 transformed = exp.StrToDate(this=value, format=fmt) 3923 elif to.this == exp.DataType.Type.DATETIME: 3924 transformed = exp.StrToTime(this=value, format=fmt) 3925 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 3926 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 3927 elif to.this == exp.DataType.Type.TEXT: 3928 transformed = exp.TimeToStr(this=value, format=fmt) 3929 3930 if not transformed: 3931 transformed = cast(this=value, to=to, safe=safe) 3932 3933 return self.sql(transformed) 3934 3935 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 3936 this = expression.this 3937 if isinstance(this, exp.JSONPathWildcard): 3938 this = self.json_path_part(this) 3939 return f".{this}" if this else "" 3940 3941 if exp.SAFE_IDENTIFIER_RE.match(this): 3942 return f".{this}" 3943 3944 this = self.json_path_part(this) 3945 return f"[{this}]" if self.JSON_PATH_BRACKETED_KEY_SUPPORTED else f".{this}" 3946 3947 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 3948 this = self.json_path_part(expression.this) 3949 return f"[{this}]" if this else "" 3950 3951 def _simplify_unless_literal(self, expression: E) -> E: 3952 if not isinstance(expression, exp.Literal): 3953 from sqlglot.optimizer.simplify import simplify 3954 3955 expression = simplify(expression, dialect=self.dialect) 3956 3957 return expression 3958 3959 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 3960 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 3961 # The first modifier here will be the one closest to the AggFunc's arg 3962 mods = sorted( 3963 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 3964 key=lambda x: 0 3965 if isinstance(x, exp.HavingMax) 3966 else (1 if isinstance(x, exp.Order) else 2), 3967 ) 3968 3969 if mods: 3970 mod = mods[0] 3971 this = expression.__class__(this=mod.this.copy()) 3972 this.meta["inline"] = True 3973 mod.this.replace(this) 3974 return self.sql(expression.this) 3975 3976 agg_func = expression.find(exp.AggFunc) 3977 3978 if agg_func: 3979 return self.sql(agg_func)[:-1] + f" {text})" 3980 3981 return f"{self.sql(expression, 'this')} {text}" 3982 3983 def _replace_line_breaks(self, string: str) -> str: 3984 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 3985 if self.pretty: 3986 return string.replace("\n", self.SENTINEL_LINE_BREAK) 3987 return string 3988 3989 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 3990 option = self.sql(expression, "this") 3991 3992 if expression.expressions: 3993 upper = option.upper() 3994 3995 # Snowflake FILE_FORMAT options are separated by whitespace 3996 sep = " " if upper == "FILE_FORMAT" else ", " 3997 3998 # Databricks copy/format options do not set their list of values with EQ 3999 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4000 values = self.expressions(expression, flat=True, sep=sep) 4001 return f"{option}{op}({values})" 4002 4003 value = self.sql(expression, "expression") 4004 4005 if not value: 4006 return option 4007 4008 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4009 4010 return f"{option}{op}{value}" 4011 4012 def credentials_sql(self, expression: exp.Credentials) -> str: 4013 cred_expr = expression.args.get("credentials") 4014 if isinstance(cred_expr, exp.Literal): 4015 # Redshift case: CREDENTIALS <string> 4016 credentials = self.sql(expression, "credentials") 4017 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4018 else: 4019 # Snowflake case: CREDENTIALS = (...) 4020 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4021 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4022 4023 storage = self.sql(expression, "storage") 4024 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4025 4026 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4027 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4028 4029 iam_role = self.sql(expression, "iam_role") 4030 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4031 4032 region = self.sql(expression, "region") 4033 region = f" REGION {region}" if region else "" 4034 4035 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4036 4037 def copy_sql(self, expression: exp.Copy) -> str: 4038 this = self.sql(expression, "this") 4039 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4040 4041 credentials = self.sql(expression, "credentials") 4042 credentials = self.seg(credentials) if credentials else "" 4043 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4044 files = self.expressions(expression, key="files", flat=True) 4045 4046 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4047 params = self.expressions( 4048 expression, 4049 key="params", 4050 sep=sep, 4051 new_line=True, 4052 skip_last=True, 4053 skip_first=True, 4054 indent=self.COPY_PARAMS_ARE_WRAPPED, 4055 ) 4056 4057 if params: 4058 if self.COPY_PARAMS_ARE_WRAPPED: 4059 params = f" WITH ({params})" 4060 elif not self.pretty: 4061 params = f" {params}" 4062 4063 return f"COPY{this}{kind} {files}{credentials}{params}" 4064 4065 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4066 return "" 4067 4068 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4069 on_sql = "ON" if expression.args.get("on") else "OFF" 4070 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4071 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4072 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4073 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4074 4075 if filter_col or retention_period: 4076 on_sql = self.func("ON", filter_col, retention_period) 4077 4078 return f"DATA_DELETION={on_sql}" 4079 4080 def maskingpolicycolumnconstraint_sql( 4081 self, expression: exp.MaskingPolicyColumnConstraint 4082 ) -> str: 4083 this = self.sql(expression, "this") 4084 expressions = self.expressions(expression, flat=True) 4085 expressions = f" USING ({expressions})" if expressions else "" 4086 return f"MASKING POLICY {this}{expressions}" 4087 4088 def gapfill_sql(self, expression: exp.GapFill) -> str: 4089 this = self.sql(expression, "this") 4090 this = f"TABLE {this}" 4091 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4092 4093 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4094 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4095 4096 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4097 this = self.sql(expression, "this") 4098 expr = expression.expression 4099 4100 if isinstance(expr, exp.Func): 4101 # T-SQL's CLR functions are case sensitive 4102 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4103 else: 4104 expr = self.sql(expression, "expression") 4105 4106 return self.scope_resolution(expr, this) 4107 4108 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4109 if self.PARSE_JSON_NAME is None: 4110 return self.sql(expression.this) 4111 4112 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4113 4114 def rand_sql(self, expression: exp.Rand) -> str: 4115 lower = self.sql(expression, "lower") 4116 upper = self.sql(expression, "upper") 4117 4118 if lower and upper: 4119 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4120 return self.func("RAND", expression.this) 4121 4122 def changes_sql(self, expression: exp.Changes) -> str: 4123 information = self.sql(expression, "information") 4124 information = f"INFORMATION => {information}" 4125 at_before = self.sql(expression, "at_before") 4126 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4127 end = self.sql(expression, "end") 4128 end = f"{self.seg('')}{end}" if end else "" 4129 4130 return f"CHANGES ({information}){at_before}{end}" 4131 4132 def pad_sql(self, expression: exp.Pad) -> str: 4133 prefix = "L" if expression.args.get("is_left") else "R" 4134 4135 fill_pattern = self.sql(expression, "fill_pattern") or None 4136 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4137 fill_pattern = "' '" 4138 4139 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4140 4141 def summarize_sql(self, expression: exp.Summarize) -> str: 4142 table = " TABLE" if expression.args.get("table") else "" 4143 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4144 4145 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4146 generate_series = exp.GenerateSeries(**expression.args) 4147 4148 parent = expression.parent 4149 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4150 parent = parent.parent 4151 4152 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4153 return self.sql(exp.Unnest(expressions=[generate_series])) 4154 4155 if isinstance(parent, exp.Select): 4156 self.unsupported("GenerateSeries projection unnesting is not supported.") 4157 4158 return self.sql(generate_series) 4159 4160 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4161 exprs = expression.expressions 4162 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4163 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4164 else: 4165 rhs = self.expressions(expression) 4166 4167 return self.func(name, expression.this, rhs) 4168 4169 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4170 if self.SUPPORTS_CONVERT_TIMEZONE: 4171 return self.function_fallback_sql(expression) 4172 4173 source_tz = expression.args.get("source_tz") 4174 target_tz = expression.args.get("target_tz") 4175 timestamp = expression.args.get("timestamp") 4176 4177 if source_tz and timestamp: 4178 timestamp = exp.AtTimeZone( 4179 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4180 ) 4181 4182 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4183 4184 return self.sql(expr) 4185 4186 def json_sql(self, expression: exp.JSON) -> str: 4187 this = self.sql(expression, "this") 4188 this = f" {this}" if this else "" 4189 4190 _with = expression.args.get("with") 4191 4192 if _with is None: 4193 with_sql = "" 4194 elif not _with: 4195 with_sql = " WITHOUT" 4196 else: 4197 with_sql = " WITH" 4198 4199 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4200 4201 return f"JSON{this}{with_sql}{unique_sql}" 4202 4203 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4204 def _generate_on_options(arg: t.Any) -> str: 4205 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4206 4207 path = self.sql(expression, "path") 4208 returning = self.sql(expression, "returning") 4209 returning = f" RETURNING {returning}" if returning else "" 4210 4211 on_condition = self.sql(expression, "on_condition") 4212 on_condition = f" {on_condition}" if on_condition else "" 4213 4214 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4215 4216 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4217 else_ = "ELSE " if expression.args.get("else_") else "" 4218 condition = self.sql(expression, "expression") 4219 condition = f"WHEN {condition} THEN " if condition else else_ 4220 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4221 return f"{condition}{insert}" 4222 4223 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4224 kind = self.sql(expression, "kind") 4225 expressions = self.seg(self.expressions(expression, sep=" ")) 4226 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4227 return res 4228 4229 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4230 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4231 empty = expression.args.get("empty") 4232 empty = ( 4233 f"DEFAULT {empty} ON EMPTY" 4234 if isinstance(empty, exp.Expression) 4235 else self.sql(expression, "empty") 4236 ) 4237 4238 error = expression.args.get("error") 4239 error = ( 4240 f"DEFAULT {error} ON ERROR" 4241 if isinstance(error, exp.Expression) 4242 else self.sql(expression, "error") 4243 ) 4244 4245 if error and empty: 4246 error = ( 4247 f"{empty} {error}" 4248 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4249 else f"{error} {empty}" 4250 ) 4251 empty = "" 4252 4253 null = self.sql(expression, "null") 4254 4255 return f"{empty}{error}{null}" 4256 4257 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4258 this = self.sql(expression, "this") 4259 path = self.sql(expression, "path") 4260 4261 passing = self.expressions(expression, "passing") 4262 passing = f" PASSING {passing}" if passing else "" 4263 4264 on_condition = self.sql(expression, "on_condition") 4265 on_condition = f" {on_condition}" if on_condition else "" 4266 4267 path = f"{path}{passing}{on_condition}" 4268 4269 return self.func("JSON_EXISTS", this, path) 4270 4271 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4272 array_agg = self.function_fallback_sql(expression) 4273 4274 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4275 parent = expression.parent 4276 if isinstance(parent, exp.Filter): 4277 parent_cond = parent.expression.this 4278 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4279 else: 4280 array_agg = f"{array_agg} FILTER(WHERE {self.sql(expression, 'this')} IS NOT NULL)" 4281 4282 return array_agg
Generator converts a given syntax tree to the corresponding SQL string.
Arguments:
- pretty: Whether to format the produced SQL string. Default: False.
- identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True or 'always': Always quote. 'safe': Only quote identifiers that are case insensitive.
- normalize: Whether to normalize identifiers to lowercase. Default: False.
- pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
- indent: The indentation size in a formatted string. For example, this affects the
indentation of subqueries and filters under a
WHEREclause. Default: 2. - normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
- unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
- max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
- leading_comma: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
- max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
- comments: Whether to preserve comments in the output SQL code. Default: True
Generator( pretty: Optional[bool] = None, identify: str | bool = False, normalize: bool = False, pad: int = 2, indent: int = 2, normalize_functions: Union[str, bool, NoneType] = None, unsupported_level: sqlglot.errors.ErrorLevel = <ErrorLevel.WARN: 'WARN'>, max_unsupported: int = 3, leading_comma: bool = False, max_text_width: int = 80, comments: bool = True, dialect: Union[str, sqlglot.dialects.dialect.Dialect, Type[sqlglot.dialects.dialect.Dialect], NoneType] = None)
598 def __init__( 599 self, 600 pretty: t.Optional[bool] = None, 601 identify: str | bool = False, 602 normalize: bool = False, 603 pad: int = 2, 604 indent: int = 2, 605 normalize_functions: t.Optional[str | bool] = None, 606 unsupported_level: ErrorLevel = ErrorLevel.WARN, 607 max_unsupported: int = 3, 608 leading_comma: bool = False, 609 max_text_width: int = 80, 610 comments: bool = True, 611 dialect: DialectType = None, 612 ): 613 import sqlglot 614 from sqlglot.dialects import Dialect 615 616 self.pretty = pretty if pretty is not None else sqlglot.pretty 617 self.identify = identify 618 self.normalize = normalize 619 self.pad = pad 620 self._indent = indent 621 self.unsupported_level = unsupported_level 622 self.max_unsupported = max_unsupported 623 self.leading_comma = leading_comma 624 self.max_text_width = max_text_width 625 self.comments = comments 626 self.dialect = Dialect.get_or_raise(dialect) 627 628 # This is both a Dialect property and a Generator argument, so we prioritize the latter 629 self.normalize_functions = ( 630 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 631 ) 632 633 self.unsupported_messages: t.List[str] = [] 634 self._escaped_quote_end: str = ( 635 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 636 ) 637 self._escaped_identifier_end: str = ( 638 self.dialect.tokenizer_class.IDENTIFIER_ESCAPES[0] + self.dialect.IDENTIFIER_END 639 ) 640 641 self._next_name = name_sequence("_t")
TRANSFORMS: Dict[Type[sqlglot.expressions.Expression], Callable[..., str]] =
{<class 'sqlglot.expressions.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TagColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithOperator'>: <function Generator.<lambda>>}
SUPPORTED_JSON_PATH_PARTS =
{<class 'sqlglot.expressions.JSONPathRecursive'>, <class 'sqlglot.expressions.JSONPathKey'>, <class 'sqlglot.expressions.JSONPathWildcard'>, <class 'sqlglot.expressions.JSONPathFilter'>, <class 'sqlglot.expressions.JSONPathUnion'>, <class 'sqlglot.expressions.JSONPathSubscript'>, <class 'sqlglot.expressions.JSONPathSelector'>, <class 'sqlglot.expressions.JSONPathSlice'>, <class 'sqlglot.expressions.JSONPathScript'>, <class 'sqlglot.expressions.JSONPathRoot'>}
TYPE_MAPPING =
{<Type.NCHAR: 'NCHAR'>: 'CHAR', <Type.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <Type.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <Type.LONGTEXT: 'LONGTEXT'>: 'TEXT', <Type.TINYTEXT: 'TINYTEXT'>: 'TEXT', <Type.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <Type.LONGBLOB: 'LONGBLOB'>: 'BLOB', <Type.TINYBLOB: 'TINYBLOB'>: 'BLOB', <Type.INET: 'INET'>: 'INET', <Type.ROWVERSION: 'ROWVERSION'>: 'VARBINARY'}
TIME_PART_SINGULARS =
{'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS =
{'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function Generator.<lambda>>, 'qualify': <function Generator.<lambda>>}
PROPERTIES_LOCATION =
{<class 'sqlglot.expressions.AllowedValuesProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AlgorithmProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.AutoIncrementProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BackupProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BlockCompressionProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CharacterSetProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ChecksumProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CollateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Cluster'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ClusteredByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistributedByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DuplicateKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DataBlocksizeProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.DataDeletionProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DefinerProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DictRange'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DynamicProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DistKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistStyleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EmptyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EngineProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExternalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.FallbackProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.FileFormatProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.FreespaceProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.GlobalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.HeapProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.InheritsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IcebergProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.InputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IsolatedLoadingProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.JournalProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.LanguageProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LikeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LocationProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockingProperty'>: <Location.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.LogProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.MaterializedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.MergeBlockRatioProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.OnProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OnCommitProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.Order'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OutputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PartitionedByProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.PartitionedOfProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PrimaryKey'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Property'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ReturnsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatDelimitedProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatSerdeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SampleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SchemaCommentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SecureProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SecurityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SerdeProperties'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Set'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SettingsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SetProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SetConfigProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SharingProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SequenceProperties'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.TransformModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.UnloggedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSystemVersioningProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>}
WITH_SEPARATED_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Command'>, <class 'sqlglot.expressions.Create'>, <class 'sqlglot.expressions.Delete'>, <class 'sqlglot.expressions.Drop'>, <class 'sqlglot.expressions.From'>, <class 'sqlglot.expressions.Insert'>, <class 'sqlglot.expressions.Join'>, <class 'sqlglot.expressions.MultitableInserts'>, <class 'sqlglot.expressions.Select'>, <class 'sqlglot.expressions.SetOperation'>, <class 'sqlglot.expressions.Update'>, <class 'sqlglot.expressions.Where'>, <class 'sqlglot.expressions.With'>)
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES =
{<Type.NCHAR: 'NCHAR'>, <Type.NVARCHAR: 'NVARCHAR'>, <Type.VARCHAR: 'VARCHAR'>, <Type.CHAR: 'CHAR'>}
643 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 644 """ 645 Generates the SQL string corresponding to the given syntax tree. 646 647 Args: 648 expression: The syntax tree. 649 copy: Whether to copy the expression. The generator performs mutations so 650 it is safer to copy. 651 652 Returns: 653 The SQL string corresponding to `expression`. 654 """ 655 if copy: 656 expression = expression.copy() 657 658 expression = self.preprocess(expression) 659 660 self.unsupported_messages = [] 661 sql = self.sql(expression).strip() 662 663 if self.pretty: 664 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 665 666 if self.unsupported_level == ErrorLevel.IGNORE: 667 return sql 668 669 if self.unsupported_level == ErrorLevel.WARN: 670 for msg in self.unsupported_messages: 671 logger.warning(msg) 672 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 673 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 674 675 return sql
Generates the SQL string corresponding to the given syntax tree.
Arguments:
- expression: The syntax tree.
- copy: Whether to copy the expression. The generator performs mutations so it is safer to copy.
Returns:
The SQL string corresponding to
expression.
def
preprocess( self, expression: sqlglot.expressions.Expression) -> sqlglot.expressions.Expression:
677 def preprocess(self, expression: exp.Expression) -> exp.Expression: 678 """Apply generic preprocessing transformations to a given expression.""" 679 if ( 680 not expression.parent 681 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 682 and any(node.parent is not expression for node in expression.find_all(exp.With)) 683 ): 684 from sqlglot.transforms import move_ctes_to_top_level 685 686 expression = move_ctes_to_top_level(expression) 687 688 if self.ENSURE_BOOLS: 689 from sqlglot.transforms import ensure_bools 690 691 expression = ensure_bools(expression) 692 693 return expression
Apply generic preprocessing transformations to a given expression.
def
maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
711 def maybe_comment( 712 self, 713 sql: str, 714 expression: t.Optional[exp.Expression] = None, 715 comments: t.Optional[t.List[str]] = None, 716 separated: bool = False, 717 ) -> str: 718 comments = ( 719 ((expression and expression.comments) if comments is None else comments) # type: ignore 720 if self.comments 721 else None 722 ) 723 724 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 725 return sql 726 727 comments_sql = " ".join( 728 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 729 ) 730 731 if not comments_sql: 732 return sql 733 734 comments_sql = self._replace_line_breaks(comments_sql) 735 736 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 737 return ( 738 f"{self.sep()}{comments_sql}{sql}" 739 if not sql or sql[0].isspace() 740 else f"{comments_sql}{self.sep()}{sql}" 741 ) 742 743 return f"{sql} {comments_sql}"
745 def wrap(self, expression: exp.Expression | str) -> str: 746 this_sql = ( 747 self.sql(expression) 748 if isinstance(expression, exp.UNWRAPPED_QUERIES) 749 else self.sql(expression, "this") 750 ) 751 if not this_sql: 752 return "()" 753 754 this_sql = self.indent(this_sql, level=1, pad=0) 755 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
def
indent( self, sql: str, level: int = 0, pad: Optional[int] = None, skip_first: bool = False, skip_last: bool = False) -> str:
771 def indent( 772 self, 773 sql: str, 774 level: int = 0, 775 pad: t.Optional[int] = None, 776 skip_first: bool = False, 777 skip_last: bool = False, 778 ) -> str: 779 if not self.pretty or not sql: 780 return sql 781 782 pad = self.pad if pad is None else pad 783 lines = sql.split("\n") 784 785 return "\n".join( 786 ( 787 line 788 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 789 else f"{' ' * (level * self._indent + pad)}{line}" 790 ) 791 for i, line in enumerate(lines) 792 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
794 def sql( 795 self, 796 expression: t.Optional[str | exp.Expression], 797 key: t.Optional[str] = None, 798 comment: bool = True, 799 ) -> str: 800 if not expression: 801 return "" 802 803 if isinstance(expression, str): 804 return expression 805 806 if key: 807 value = expression.args.get(key) 808 if value: 809 return self.sql(value) 810 return "" 811 812 transform = self.TRANSFORMS.get(expression.__class__) 813 814 if callable(transform): 815 sql = transform(self, expression) 816 elif isinstance(expression, exp.Expression): 817 exp_handler_name = f"{expression.key}_sql" 818 819 if hasattr(self, exp_handler_name): 820 sql = getattr(self, exp_handler_name)(expression) 821 elif isinstance(expression, exp.Func): 822 sql = self.function_fallback_sql(expression) 823 elif isinstance(expression, exp.Property): 824 sql = self.property_sql(expression) 825 else: 826 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 827 else: 828 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 829 830 return self.maybe_comment(sql, expression) if self.comments and comment else sql
837 def cache_sql(self, expression: exp.Cache) -> str: 838 lazy = " LAZY" if expression.args.get("lazy") else "" 839 table = self.sql(expression, "this") 840 options = expression.args.get("options") 841 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 842 sql = self.sql(expression, "expression") 843 sql = f" AS{self.sep()}{sql}" if sql else "" 844 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 845 return self.prepend_ctes(expression, sql)
847 def characterset_sql(self, expression: exp.CharacterSet) -> str: 848 if isinstance(expression.parent, exp.Cast): 849 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 850 default = "DEFAULT " if expression.args.get("default") else "" 851 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
865 def column_sql(self, expression: exp.Column) -> str: 866 join_mark = " (+)" if expression.args.get("join_mark") else "" 867 868 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 869 join_mark = "" 870 self.unsupported("Outer join syntax using the (+) operator is not supported.") 871 872 return f"{self.column_parts(expression)}{join_mark}"
880 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 881 column = self.sql(expression, "this") 882 kind = self.sql(expression, "kind") 883 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 884 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 885 kind = f"{sep}{kind}" if kind else "" 886 constraints = f" {constraints}" if constraints else "" 887 position = self.sql(expression, "position") 888 position = f" {position}" if position else "" 889 890 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 891 kind = "" 892 893 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
900 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 901 this = self.sql(expression, "this") 902 if expression.args.get("not_null"): 903 persisted = " PERSISTED NOT NULL" 904 elif expression.args.get("persisted"): 905 persisted = " PERSISTED" 906 else: 907 persisted = "" 908 return f"AS {this}{persisted}"
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
921 def generatedasidentitycolumnconstraint_sql( 922 self, expression: exp.GeneratedAsIdentityColumnConstraint 923 ) -> str: 924 this = "" 925 if expression.this is not None: 926 on_null = " ON NULL" if expression.args.get("on_null") else "" 927 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 928 929 start = expression.args.get("start") 930 start = f"START WITH {start}" if start else "" 931 increment = expression.args.get("increment") 932 increment = f" INCREMENT BY {increment}" if increment else "" 933 minvalue = expression.args.get("minvalue") 934 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 935 maxvalue = expression.args.get("maxvalue") 936 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 937 cycle = expression.args.get("cycle") 938 cycle_sql = "" 939 940 if cycle is not None: 941 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 942 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 943 944 sequence_opts = "" 945 if start or increment or cycle_sql: 946 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 947 sequence_opts = f" ({sequence_opts.strip()})" 948 949 expr = self.sql(expression, "expression") 950 expr = f"({expr})" if expr else "IDENTITY" 951 952 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
def
periodforsystemtimeconstraint_sql( self, expression: sqlglot.expressions.PeriodForSystemTimeConstraint) -> str:
def
notnullcolumnconstraint_sql(self, expression: sqlglot.expressions.NotNullColumnConstraint) -> str:
def
transformcolumnconstraint_sql(self, expression: sqlglot.expressions.TransformColumnConstraint) -> str:
def
primarykeycolumnconstraint_sql(self, expression: sqlglot.expressions.PrimaryKeyColumnConstraint) -> str:
def
uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
978 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 979 this = self.sql(expression, "this") 980 this = f" {this}" if this else "" 981 index_type = expression.args.get("index_type") 982 index_type = f" USING {index_type}" if index_type else "" 983 on_conflict = self.sql(expression, "on_conflict") 984 on_conflict = f" {on_conflict}" if on_conflict else "" 985 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 986 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}"
991 def create_sql(self, expression: exp.Create) -> str: 992 kind = self.sql(expression, "kind") 993 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 994 properties = expression.args.get("properties") 995 properties_locs = self.locate_properties(properties) if properties else defaultdict() 996 997 this = self.createable_sql(expression, properties_locs) 998 999 properties_sql = "" 1000 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1001 exp.Properties.Location.POST_WITH 1002 ): 1003 properties_sql = self.sql( 1004 exp.Properties( 1005 expressions=[ 1006 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1007 *properties_locs[exp.Properties.Location.POST_WITH], 1008 ] 1009 ) 1010 ) 1011 1012 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1013 properties_sql = self.sep() + properties_sql 1014 elif not self.pretty: 1015 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1016 properties_sql = f" {properties_sql}" 1017 1018 begin = " BEGIN" if expression.args.get("begin") else "" 1019 end = " END" if expression.args.get("end") else "" 1020 1021 expression_sql = self.sql(expression, "expression") 1022 if expression_sql: 1023 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1024 1025 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1026 postalias_props_sql = "" 1027 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1028 postalias_props_sql = self.properties( 1029 exp.Properties( 1030 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1031 ), 1032 wrapped=False, 1033 ) 1034 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1035 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1036 1037 postindex_props_sql = "" 1038 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1039 postindex_props_sql = self.properties( 1040 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1041 wrapped=False, 1042 prefix=" ", 1043 ) 1044 1045 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1046 indexes = f" {indexes}" if indexes else "" 1047 index_sql = indexes + postindex_props_sql 1048 1049 replace = " OR REPLACE" if expression.args.get("replace") else "" 1050 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1051 unique = " UNIQUE" if expression.args.get("unique") else "" 1052 1053 clustered = expression.args.get("clustered") 1054 if clustered is None: 1055 clustered_sql = "" 1056 elif clustered: 1057 clustered_sql = " CLUSTERED COLUMNSTORE" 1058 else: 1059 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1060 1061 postcreate_props_sql = "" 1062 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1063 postcreate_props_sql = self.properties( 1064 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1065 sep=" ", 1066 prefix=" ", 1067 wrapped=False, 1068 ) 1069 1070 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1071 1072 postexpression_props_sql = "" 1073 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1074 postexpression_props_sql = self.properties( 1075 exp.Properties( 1076 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1077 ), 1078 sep=" ", 1079 prefix=" ", 1080 wrapped=False, 1081 ) 1082 1083 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1084 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1085 no_schema_binding = ( 1086 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1087 ) 1088 1089 clone = self.sql(expression, "clone") 1090 clone = f" {clone}" if clone else "" 1091 1092 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_sql}{expression_sql}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1093 return self.prepend_ctes(expression, expression_sql)
1095 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1096 start = self.sql(expression, "start") 1097 start = f"START WITH {start}" if start else "" 1098 increment = self.sql(expression, "increment") 1099 increment = f" INCREMENT BY {increment}" if increment else "" 1100 minvalue = self.sql(expression, "minvalue") 1101 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1102 maxvalue = self.sql(expression, "maxvalue") 1103 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1104 owned = self.sql(expression, "owned") 1105 owned = f" OWNED BY {owned}" if owned else "" 1106 1107 cache = expression.args.get("cache") 1108 if cache is None: 1109 cache_str = "" 1110 elif cache is True: 1111 cache_str = " CACHE" 1112 else: 1113 cache_str = f" CACHE {cache}" 1114 1115 options = self.expressions(expression, key="options", flat=True, sep=" ") 1116 options = f" {options}" if options else "" 1117 1118 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1120 def clone_sql(self, expression: exp.Clone) -> str: 1121 this = self.sql(expression, "this") 1122 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1123 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1124 return f"{shallow}{keyword} {this}"
1126 def describe_sql(self, expression: exp.Describe) -> str: 1127 style = expression.args.get("style") 1128 style = f" {style}" if style else "" 1129 partition = self.sql(expression, "partition") 1130 partition = f" {partition}" if partition else "" 1131 return f"DESCRIBE{style} {self.sql(expression, 'this')}{partition}"
1153 def cte_sql(self, expression: exp.CTE) -> str: 1154 alias = self.sql(expression, "alias") 1155 1156 materialized = expression.args.get("materialized") 1157 if materialized is False: 1158 materialized = "NOT MATERIALIZED " 1159 elif materialized: 1160 materialized = "MATERIALIZED " 1161 1162 return f"{alias} AS {materialized or ''}{self.wrap(expression)}"
1164 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1165 alias = self.sql(expression, "this") 1166 columns = self.expressions(expression, key="columns", flat=True) 1167 columns = f"({columns})" if columns else "" 1168 1169 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1170 columns = "" 1171 self.unsupported("Named columns are not supported in table alias.") 1172 1173 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1174 alias = self._next_name() 1175 1176 return f"{alias}{columns}"
1196 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1197 this = self.sql(expression, "this") 1198 escape = expression.args.get("escape") 1199 1200 if self.dialect.UNICODE_START: 1201 escape_substitute = r"\\\1" 1202 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1203 else: 1204 escape_substitute = r"\\u\1" 1205 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1206 1207 if escape: 1208 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1209 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1210 else: 1211 escape_pattern = ESCAPED_UNICODE_RE 1212 escape_sql = "" 1213 1214 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1215 this = escape_pattern.sub(escape_substitute, this) 1216 1217 return f"{left_quote}{this}{right_quote}{escape_sql}"
1229 def datatype_sql(self, expression: exp.DataType) -> str: 1230 nested = "" 1231 values = "" 1232 interior = self.expressions(expression, flat=True) 1233 1234 type_value = expression.this 1235 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1236 type_sql = self.sql(expression, "kind") 1237 elif type_value != exp.DataType.Type.NULLABLE or self.SUPPORTS_NULLABLE_TYPES: 1238 type_sql = ( 1239 self.TYPE_MAPPING.get(type_value, type_value.value) 1240 if isinstance(type_value, exp.DataType.Type) 1241 else type_value 1242 ) 1243 else: 1244 return interior 1245 1246 if interior: 1247 if expression.args.get("nested"): 1248 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1249 if expression.args.get("values") is not None: 1250 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1251 values = self.expressions(expression, key="values", flat=True) 1252 values = f"{delimiters[0]}{values}{delimiters[1]}" 1253 elif type_value == exp.DataType.Type.INTERVAL: 1254 nested = f" {interior}" 1255 else: 1256 nested = f"({interior})" 1257 1258 type_sql = f"{type_sql}{nested}{values}" 1259 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1260 exp.DataType.Type.TIMETZ, 1261 exp.DataType.Type.TIMESTAMPTZ, 1262 ): 1263 type_sql = f"{type_sql} WITH TIME ZONE" 1264 1265 return type_sql
1267 def directory_sql(self, expression: exp.Directory) -> str: 1268 local = "LOCAL " if expression.args.get("local") else "" 1269 row_format = self.sql(expression, "row_format") 1270 row_format = f" {row_format}" if row_format else "" 1271 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1273 def delete_sql(self, expression: exp.Delete) -> str: 1274 this = self.sql(expression, "this") 1275 this = f" FROM {this}" if this else "" 1276 using = self.sql(expression, "using") 1277 using = f" USING {using}" if using else "" 1278 where = self.sql(expression, "where") 1279 returning = self.sql(expression, "returning") 1280 limit = self.sql(expression, "limit") 1281 tables = self.expressions(expression, key="tables") 1282 tables = f" {tables}" if tables else "" 1283 if self.RETURNING_END: 1284 expression_sql = f"{this}{using}{where}{returning}{limit}" 1285 else: 1286 expression_sql = f"{returning}{this}{using}{where}{limit}" 1287 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1289 def drop_sql(self, expression: exp.Drop) -> str: 1290 this = self.sql(expression, "this") 1291 expressions = self.expressions(expression, flat=True) 1292 expressions = f" ({expressions})" if expressions else "" 1293 kind = expression.args["kind"] 1294 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1295 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1296 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1297 on_cluster = self.sql(expression, "cluster") 1298 on_cluster = f" {on_cluster}" if on_cluster else "" 1299 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1300 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1301 cascade = " CASCADE" if expression.args.get("cascade") else "" 1302 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1303 purge = " PURGE" if expression.args.get("purge") else "" 1304 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1306 def set_operation(self, expression: exp.SetOperation) -> str: 1307 op_type = type(expression) 1308 op_name = op_type.key.upper() 1309 1310 distinct = expression.args.get("distinct") 1311 if ( 1312 distinct is False 1313 and op_type in (exp.Except, exp.Intersect) 1314 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1315 ): 1316 self.unsupported(f"{op_name} ALL is not supported") 1317 1318 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1319 1320 if distinct is None: 1321 distinct = default_distinct 1322 if distinct is None: 1323 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1324 1325 if distinct is default_distinct: 1326 kind = "" 1327 else: 1328 kind = " DISTINCT" if distinct else " ALL" 1329 1330 by_name = " BY NAME" if expression.args.get("by_name") else "" 1331 return f"{op_name}{kind}{by_name}"
1333 def set_operations(self, expression: exp.SetOperation) -> str: 1334 if not self.SET_OP_MODIFIERS: 1335 limit = expression.args.get("limit") 1336 order = expression.args.get("order") 1337 1338 if limit or order: 1339 select = exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1340 1341 if limit: 1342 select = select.limit(limit.pop(), copy=False) 1343 if order: 1344 select = select.order_by(order.pop(), copy=False) 1345 return self.sql(select) 1346 1347 sqls: t.List[str] = [] 1348 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1349 1350 while stack: 1351 node = stack.pop() 1352 1353 if isinstance(node, exp.SetOperation): 1354 stack.append(node.expression) 1355 stack.append( 1356 self.maybe_comment( 1357 self.set_operation(node), comments=node.comments, separated=True 1358 ) 1359 ) 1360 stack.append(node.this) 1361 else: 1362 sqls.append(self.sql(node)) 1363 1364 this = self.sep().join(sqls) 1365 this = self.query_modifiers(expression, this) 1366 return self.prepend_ctes(expression, this)
1368 def fetch_sql(self, expression: exp.Fetch) -> str: 1369 direction = expression.args.get("direction") 1370 direction = f" {direction}" if direction else "" 1371 count = self.sql(expression, "count") 1372 count = f" {count}" if count else "" 1373 if expression.args.get("percent"): 1374 count = f"{count} PERCENT" 1375 with_ties_or_only = "WITH TIES" if expression.args.get("with_ties") else "ONLY" 1376 return f"{self.seg('FETCH')}{direction}{count} ROWS {with_ties_or_only}"
1378 def filter_sql(self, expression: exp.Filter) -> str: 1379 if self.AGGREGATE_FILTER_SUPPORTED: 1380 this = self.sql(expression, "this") 1381 where = self.sql(expression, "expression").strip() 1382 return f"{this} FILTER({where})" 1383 1384 agg = expression.this 1385 agg_arg = agg.this 1386 cond = expression.expression.this 1387 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1388 return self.sql(agg)
1397 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1398 using = self.sql(expression, "using") 1399 using = f" USING {using}" if using else "" 1400 columns = self.expressions(expression, key="columns", flat=True) 1401 columns = f"({columns})" if columns else "" 1402 partition_by = self.expressions(expression, key="partition_by", flat=True) 1403 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1404 where = self.sql(expression, "where") 1405 include = self.expressions(expression, key="include", flat=True) 1406 if include: 1407 include = f" INCLUDE ({include})" 1408 with_storage = self.expressions(expression, key="with_storage", flat=True) 1409 with_storage = f" WITH ({with_storage})" if with_storage else "" 1410 tablespace = self.sql(expression, "tablespace") 1411 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1412 on = self.sql(expression, "on") 1413 on = f" ON {on}" if on else "" 1414 1415 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1417 def index_sql(self, expression: exp.Index) -> str: 1418 unique = "UNIQUE " if expression.args.get("unique") else "" 1419 primary = "PRIMARY " if expression.args.get("primary") else "" 1420 amp = "AMP " if expression.args.get("amp") else "" 1421 name = self.sql(expression, "this") 1422 name = f"{name} " if name else "" 1423 table = self.sql(expression, "table") 1424 table = f"{self.INDEX_ON} {table}" if table else "" 1425 1426 index = "INDEX " if not table else "" 1427 1428 params = self.sql(expression, "params") 1429 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1431 def identifier_sql(self, expression: exp.Identifier) -> str: 1432 text = expression.name 1433 lower = text.lower() 1434 text = lower if self.normalize and not expression.quoted else text 1435 text = text.replace(self.dialect.IDENTIFIER_END, self._escaped_identifier_end) 1436 if ( 1437 expression.quoted 1438 or self.dialect.can_identify(text, self.identify) 1439 or lower in self.RESERVED_KEYWORDS 1440 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1441 ): 1442 text = f"{self.dialect.IDENTIFIER_START}{text}{self.dialect.IDENTIFIER_END}" 1443 return text
1458 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1459 input_format = self.sql(expression, "input_format") 1460 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1461 output_format = self.sql(expression, "output_format") 1462 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1463 return self.sep().join((input_format, output_format))
1472 def properties_sql(self, expression: exp.Properties) -> str: 1473 root_properties = [] 1474 with_properties = [] 1475 1476 for p in expression.expressions: 1477 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1478 if p_loc == exp.Properties.Location.POST_WITH: 1479 with_properties.append(p) 1480 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1481 root_properties.append(p) 1482 1483 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1484 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1485 1486 if root_props and with_props and not self.pretty: 1487 with_props = " " + with_props 1488 1489 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1496 def properties( 1497 self, 1498 properties: exp.Properties, 1499 prefix: str = "", 1500 sep: str = ", ", 1501 suffix: str = "", 1502 wrapped: bool = True, 1503 ) -> str: 1504 if properties.expressions: 1505 expressions = self.expressions(properties, sep=sep, indent=False) 1506 if expressions: 1507 expressions = self.wrap(expressions) if wrapped else expressions 1508 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1509 return ""
1514 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1515 properties_locs = defaultdict(list) 1516 for p in properties.expressions: 1517 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1518 if p_loc != exp.Properties.Location.UNSUPPORTED: 1519 properties_locs[p_loc].append(p) 1520 else: 1521 self.unsupported(f"Unsupported property {p.key}") 1522 1523 return properties_locs
def
property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1530 def property_sql(self, expression: exp.Property) -> str: 1531 property_cls = expression.__class__ 1532 if property_cls == exp.Property: 1533 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1534 1535 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1536 if not property_name: 1537 self.unsupported(f"Unsupported property {expression.key}") 1538 1539 return f"{property_name}={self.sql(expression, 'this')}"
1541 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1542 if self.SUPPORTS_CREATE_TABLE_LIKE: 1543 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1544 options = f" {options}" if options else "" 1545 1546 like = f"LIKE {self.sql(expression, 'this')}{options}" 1547 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1548 like = f"({like})" 1549 1550 return like 1551 1552 if expression.expressions: 1553 self.unsupported("Transpilation of LIKE property options is unsupported") 1554 1555 select = exp.select("*").from_(expression.this).limit(0) 1556 return f"AS {self.sql(select)}"
1563 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1564 no = "NO " if expression.args.get("no") else "" 1565 local = expression.args.get("local") 1566 local = f"{local} " if local else "" 1567 dual = "DUAL " if expression.args.get("dual") else "" 1568 before = "BEFORE " if expression.args.get("before") else "" 1569 after = "AFTER " if expression.args.get("after") else "" 1570 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1586 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1587 if expression.args.get("no"): 1588 return "NO MERGEBLOCKRATIO" 1589 if expression.args.get("default"): 1590 return "DEFAULT MERGEBLOCKRATIO" 1591 1592 percent = " PERCENT" if expression.args.get("percent") else "" 1593 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1595 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1596 default = expression.args.get("default") 1597 minimum = expression.args.get("minimum") 1598 maximum = expression.args.get("maximum") 1599 if default or minimum or maximum: 1600 if default: 1601 prop = "DEFAULT" 1602 elif minimum: 1603 prop = "MINIMUM" 1604 else: 1605 prop = "MAXIMUM" 1606 return f"{prop} DATABLOCKSIZE" 1607 units = expression.args.get("units") 1608 units = f" {units}" if units else "" 1609 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1611 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1612 autotemp = expression.args.get("autotemp") 1613 always = expression.args.get("always") 1614 default = expression.args.get("default") 1615 manual = expression.args.get("manual") 1616 never = expression.args.get("never") 1617 1618 if autotemp is not None: 1619 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1620 elif always: 1621 prop = "ALWAYS" 1622 elif default: 1623 prop = "DEFAULT" 1624 elif manual: 1625 prop = "MANUAL" 1626 elif never: 1627 prop = "NEVER" 1628 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1630 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1631 no = expression.args.get("no") 1632 no = " NO" if no else "" 1633 concurrent = expression.args.get("concurrent") 1634 concurrent = " CONCURRENT" if concurrent else "" 1635 target = self.sql(expression, "target") 1636 target = f" {target}" if target else "" 1637 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1639 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1640 if isinstance(expression.this, list): 1641 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1642 if expression.this: 1643 modulus = self.sql(expression, "this") 1644 remainder = self.sql(expression, "expression") 1645 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1646 1647 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1648 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1649 return f"FROM ({from_expressions}) TO ({to_expressions})"
1651 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1652 this = self.sql(expression, "this") 1653 1654 for_values_or_default = expression.expression 1655 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1656 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1657 else: 1658 for_values_or_default = " DEFAULT" 1659 1660 return f"PARTITION OF {this}{for_values_or_default}"
1662 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1663 kind = expression.args.get("kind") 1664 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1665 for_or_in = expression.args.get("for_or_in") 1666 for_or_in = f" {for_or_in}" if for_or_in else "" 1667 lock_type = expression.args.get("lock_type") 1668 override = " OVERRIDE" if expression.args.get("override") else "" 1669 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1671 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1672 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1673 statistics = expression.args.get("statistics") 1674 statistics_sql = "" 1675 if statistics is not None: 1676 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1677 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1679 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1680 this = self.sql(expression, "this") 1681 this = f"HISTORY_TABLE={this}" if this else "" 1682 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1683 data_consistency = ( 1684 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1685 ) 1686 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1687 retention_period = ( 1688 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1689 ) 1690 1691 if this: 1692 on_sql = self.func("ON", this, data_consistency, retention_period) 1693 else: 1694 on_sql = "ON" if expression.args.get("on") else "OFF" 1695 1696 sql = f"SYSTEM_VERSIONING={on_sql}" 1697 1698 return f"WITH({sql})" if expression.args.get("with") else sql
1700 def insert_sql(self, expression: exp.Insert) -> str: 1701 hint = self.sql(expression, "hint") 1702 overwrite = expression.args.get("overwrite") 1703 1704 if isinstance(expression.this, exp.Directory): 1705 this = " OVERWRITE" if overwrite else " INTO" 1706 else: 1707 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1708 1709 stored = self.sql(expression, "stored") 1710 stored = f" {stored}" if stored else "" 1711 alternative = expression.args.get("alternative") 1712 alternative = f" OR {alternative}" if alternative else "" 1713 ignore = " IGNORE" if expression.args.get("ignore") else "" 1714 is_function = expression.args.get("is_function") 1715 if is_function: 1716 this = f"{this} FUNCTION" 1717 this = f"{this} {self.sql(expression, 'this')}" 1718 1719 exists = " IF EXISTS" if expression.args.get("exists") else "" 1720 where = self.sql(expression, "where") 1721 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1722 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1723 on_conflict = self.sql(expression, "conflict") 1724 on_conflict = f" {on_conflict}" if on_conflict else "" 1725 by_name = " BY NAME" if expression.args.get("by_name") else "" 1726 returning = self.sql(expression, "returning") 1727 1728 if self.RETURNING_END: 1729 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1730 else: 1731 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1732 1733 partition_by = self.sql(expression, "partition") 1734 partition_by = f" {partition_by}" if partition_by else "" 1735 settings = self.sql(expression, "settings") 1736 settings = f" {settings}" if settings else "" 1737 1738 source = self.sql(expression, "source") 1739 source = f"TABLE {source}" if source else "" 1740 1741 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1742 return self.prepend_ctes(expression, sql)
1760 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1761 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1762 1763 constraint = self.sql(expression, "constraint") 1764 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1765 1766 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1767 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1768 action = self.sql(expression, "action") 1769 1770 expressions = self.expressions(expression, flat=True) 1771 if expressions: 1772 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1773 expressions = f" {set_keyword}{expressions}" 1774 1775 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
1780 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1781 fields = self.sql(expression, "fields") 1782 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1783 escaped = self.sql(expression, "escaped") 1784 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1785 items = self.sql(expression, "collection_items") 1786 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1787 keys = self.sql(expression, "map_keys") 1788 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1789 lines = self.sql(expression, "lines") 1790 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1791 null = self.sql(expression, "null") 1792 null = f" NULL DEFINED AS {null}" if null else "" 1793 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
1821 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 1822 table = self.table_parts(expression) 1823 only = "ONLY " if expression.args.get("only") else "" 1824 partition = self.sql(expression, "partition") 1825 partition = f" {partition}" if partition else "" 1826 version = self.sql(expression, "version") 1827 version = f" {version}" if version else "" 1828 alias = self.sql(expression, "alias") 1829 alias = f"{sep}{alias}" if alias else "" 1830 1831 sample = self.sql(expression, "sample") 1832 if self.dialect.ALIAS_POST_TABLESAMPLE: 1833 sample_pre_alias = sample 1834 sample_post_alias = "" 1835 else: 1836 sample_pre_alias = "" 1837 sample_post_alias = sample 1838 1839 hints = self.expressions(expression, key="hints", sep=" ") 1840 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 1841 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 1842 joins = self.indent( 1843 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 1844 ) 1845 laterals = self.expressions(expression, key="laterals", sep="") 1846 1847 file_format = self.sql(expression, "format") 1848 if file_format: 1849 pattern = self.sql(expression, "pattern") 1850 pattern = f", PATTERN => {pattern}" if pattern else "" 1851 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 1852 1853 ordinality = expression.args.get("ordinality") or "" 1854 if ordinality: 1855 ordinality = f" WITH ORDINALITY{alias}" 1856 alias = "" 1857 1858 when = self.sql(expression, "when") 1859 if when: 1860 table = f"{table} {when}" 1861 1862 changes = self.sql(expression, "changes") 1863 changes = f" {changes}" if changes else "" 1864 1865 rows_from = self.expressions(expression, key="rows_from") 1866 if rows_from: 1867 table = f"ROWS FROM {self.wrap(rows_from)}" 1868 1869 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
def
tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
1871 def tablesample_sql( 1872 self, 1873 expression: exp.TableSample, 1874 tablesample_keyword: t.Optional[str] = None, 1875 ) -> str: 1876 method = self.sql(expression, "method") 1877 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 1878 numerator = self.sql(expression, "bucket_numerator") 1879 denominator = self.sql(expression, "bucket_denominator") 1880 field = self.sql(expression, "bucket_field") 1881 field = f" ON {field}" if field else "" 1882 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 1883 seed = self.sql(expression, "seed") 1884 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 1885 1886 size = self.sql(expression, "size") 1887 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 1888 size = f"{size} ROWS" 1889 1890 percent = self.sql(expression, "percent") 1891 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 1892 percent = f"{percent} PERCENT" 1893 1894 expr = f"{bucket}{percent}{size}" 1895 if self.TABLESAMPLE_REQUIRES_PARENS: 1896 expr = f"({expr})" 1897 1898 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
1900 def pivot_sql(self, expression: exp.Pivot) -> str: 1901 expressions = self.expressions(expression, flat=True) 1902 1903 if expression.this: 1904 this = self.sql(expression, "this") 1905 if not expressions: 1906 return f"UNPIVOT {this}" 1907 1908 on = f"{self.seg('ON')} {expressions}" 1909 using = self.expressions(expression, key="using", flat=True) 1910 using = f"{self.seg('USING')} {using}" if using else "" 1911 group = self.sql(expression, "group") 1912 return f"PIVOT {this}{on}{using}{group}" 1913 1914 alias = self.sql(expression, "alias") 1915 alias = f" AS {alias}" if alias else "" 1916 direction = self.seg("UNPIVOT" if expression.unpivot else "PIVOT") 1917 1918 field = self.sql(expression, "field") 1919 if field and isinstance(expression.args.get("field"), exp.PivotAny): 1920 field = f"IN ({field})" 1921 1922 include_nulls = expression.args.get("include_nulls") 1923 if include_nulls is not None: 1924 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 1925 else: 1926 nulls = "" 1927 1928 default_on_null = self.sql(expression, "default_on_null") 1929 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 1930 return f"{direction}{nulls}({expressions} FOR {field}{default_on_null}){alias}"
1941 def update_sql(self, expression: exp.Update) -> str: 1942 this = self.sql(expression, "this") 1943 set_sql = self.expressions(expression, flat=True) 1944 from_sql = self.sql(expression, "from") 1945 where_sql = self.sql(expression, "where") 1946 returning = self.sql(expression, "returning") 1947 order = self.sql(expression, "order") 1948 limit = self.sql(expression, "limit") 1949 if self.RETURNING_END: 1950 expression_sql = f"{from_sql}{where_sql}{returning}" 1951 else: 1952 expression_sql = f"{returning}{from_sql}{where_sql}" 1953 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 1954 return self.prepend_ctes(expression, sql)
1956 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 1957 values_as_table = values_as_table and self.VALUES_AS_TABLE 1958 1959 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 1960 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 1961 args = self.expressions(expression) 1962 alias = self.sql(expression, "alias") 1963 values = f"VALUES{self.seg('')}{args}" 1964 values = ( 1965 f"({values})" 1966 if self.WRAP_DERIVED_VALUES and (alias or isinstance(expression.parent, exp.From)) 1967 else values 1968 ) 1969 return f"{values} AS {alias}" if alias else values 1970 1971 # Converts `VALUES...` expression into a series of select unions. 1972 alias_node = expression.args.get("alias") 1973 column_names = alias_node and alias_node.columns 1974 1975 selects: t.List[exp.Query] = [] 1976 1977 for i, tup in enumerate(expression.expressions): 1978 row = tup.expressions 1979 1980 if i == 0 and column_names: 1981 row = [ 1982 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 1983 ] 1984 1985 selects.append(exp.Select(expressions=row)) 1986 1987 if self.pretty: 1988 # This may result in poor performance for large-cardinality `VALUES` tables, due to 1989 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 1990 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 1991 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 1992 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 1993 1994 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 1995 unions = " UNION ALL ".join(self.sql(select) for select in selects) 1996 return f"({unions}){alias}"
2021 def group_sql(self, expression: exp.Group) -> str: 2022 group_by_all = expression.args.get("all") 2023 if group_by_all is True: 2024 modifier = " ALL" 2025 elif group_by_all is False: 2026 modifier = " DISTINCT" 2027 else: 2028 modifier = "" 2029 2030 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2031 2032 grouping_sets = self.expressions(expression, key="grouping_sets") 2033 cube = self.expressions(expression, key="cube") 2034 rollup = self.expressions(expression, key="rollup") 2035 2036 groupings = csv( 2037 self.seg(grouping_sets) if grouping_sets else "", 2038 self.seg(cube) if cube else "", 2039 self.seg(rollup) if rollup else "", 2040 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2041 sep=self.GROUPINGS_SEP, 2042 ) 2043 2044 if ( 2045 expression.expressions 2046 and groupings 2047 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2048 ): 2049 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2050 2051 return f"{group_by}{groupings}"
2057 def connect_sql(self, expression: exp.Connect) -> str: 2058 start = self.sql(expression, "start") 2059 start = self.seg(f"START WITH {start}") if start else "" 2060 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2061 connect = self.sql(expression, "connect") 2062 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2063 return start + connect
2068 def join_sql(self, expression: exp.Join) -> str: 2069 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2070 side = None 2071 else: 2072 side = expression.side 2073 2074 op_sql = " ".join( 2075 op 2076 for op in ( 2077 expression.method, 2078 "GLOBAL" if expression.args.get("global") else None, 2079 side, 2080 expression.kind, 2081 expression.hint if self.JOIN_HINTS else None, 2082 ) 2083 if op 2084 ) 2085 match_cond = self.sql(expression, "match_condition") 2086 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2087 on_sql = self.sql(expression, "on") 2088 using = expression.args.get("using") 2089 2090 if not on_sql and using: 2091 on_sql = csv(*(self.sql(column) for column in using)) 2092 2093 this = expression.this 2094 this_sql = self.sql(this) 2095 2096 if on_sql: 2097 on_sql = self.indent(on_sql, skip_first=True) 2098 space = self.seg(" " * self.pad) if self.pretty else " " 2099 if using: 2100 on_sql = f"{space}USING ({on_sql})" 2101 else: 2102 on_sql = f"{space}ON {on_sql}" 2103 elif not op_sql: 2104 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2105 return f" {this_sql}" 2106 2107 return f", {this_sql}" 2108 2109 if op_sql != "STRAIGHT_JOIN": 2110 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2111 2112 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}"
2119 def lateral_op(self, expression: exp.Lateral) -> str: 2120 cross_apply = expression.args.get("cross_apply") 2121 2122 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2123 if cross_apply is True: 2124 op = "INNER JOIN " 2125 elif cross_apply is False: 2126 op = "LEFT JOIN " 2127 else: 2128 op = "" 2129 2130 return f"{op}LATERAL"
2132 def lateral_sql(self, expression: exp.Lateral) -> str: 2133 this = self.sql(expression, "this") 2134 2135 if expression.args.get("view"): 2136 alias = expression.args["alias"] 2137 columns = self.expressions(alias, key="columns", flat=True) 2138 table = f" {alias.name}" if alias.name else "" 2139 columns = f" AS {columns}" if columns else "" 2140 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2141 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2142 2143 alias = self.sql(expression, "alias") 2144 alias = f" AS {alias}" if alias else "" 2145 return f"{self.lateral_op(expression)} {this}{alias}"
2147 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2148 this = self.sql(expression, "this") 2149 2150 args = [ 2151 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2152 for e in (expression.args.get(k) for k in ("offset", "expression")) 2153 if e 2154 ] 2155 2156 args_sql = ", ".join(self.sql(e) for e in args) 2157 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2158 expressions = self.expressions(expression, flat=True) 2159 expressions = f" BY {expressions}" if expressions else "" 2160 2161 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{expressions}"
2163 def offset_sql(self, expression: exp.Offset) -> str: 2164 this = self.sql(expression, "this") 2165 value = expression.expression 2166 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2167 expressions = self.expressions(expression, flat=True) 2168 expressions = f" BY {expressions}" if expressions else "" 2169 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2171 def setitem_sql(self, expression: exp.SetItem) -> str: 2172 kind = self.sql(expression, "kind") 2173 kind = f"{kind} " if kind else "" 2174 this = self.sql(expression, "this") 2175 expressions = self.expressions(expression) 2176 collate = self.sql(expression, "collate") 2177 collate = f" COLLATE {collate}" if collate else "" 2178 global_ = "GLOBAL " if expression.args.get("global") else "" 2179 return f"{global_}{kind}{this}{expressions}{collate}"
2181 def set_sql(self, expression: exp.Set) -> str: 2182 expressions = ( 2183 f" {self.expressions(expression, flat=True)}" if expression.expressions else "" 2184 ) 2185 tag = " TAG" if expression.args.get("tag") else "" 2186 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2191 def lock_sql(self, expression: exp.Lock) -> str: 2192 if not self.LOCKING_READS_SUPPORTED: 2193 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2194 return "" 2195 2196 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 2197 expressions = self.expressions(expression, flat=True) 2198 expressions = f" OF {expressions}" if expressions else "" 2199 wait = expression.args.get("wait") 2200 2201 if wait is not None: 2202 if isinstance(wait, exp.Literal): 2203 wait = f" WAIT {self.sql(wait)}" 2204 else: 2205 wait = " NOWAIT" if wait else " SKIP LOCKED" 2206 2207 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str(self, text: str, escape_backslash: bool = True) -> str:
2215 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2216 if self.dialect.ESCAPED_SEQUENCES: 2217 to_escaped = self.dialect.ESCAPED_SEQUENCES 2218 text = "".join( 2219 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2220 ) 2221 2222 return self._replace_line_breaks(text).replace( 2223 self.dialect.QUOTE_END, self._escaped_quote_end 2224 )
2226 def loaddata_sql(self, expression: exp.LoadData) -> str: 2227 local = " LOCAL" if expression.args.get("local") else "" 2228 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2229 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2230 this = f" INTO TABLE {self.sql(expression, 'this')}" 2231 partition = self.sql(expression, "partition") 2232 partition = f" {partition}" if partition else "" 2233 input_format = self.sql(expression, "input_format") 2234 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2235 serde = self.sql(expression, "serde") 2236 serde = f" SERDE {serde}" if serde else "" 2237 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2245 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2246 this = self.sql(expression, "this") 2247 this = f"{this} " if this else this 2248 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2249 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2251 def withfill_sql(self, expression: exp.WithFill) -> str: 2252 from_sql = self.sql(expression, "from") 2253 from_sql = f" FROM {from_sql}" if from_sql else "" 2254 to_sql = self.sql(expression, "to") 2255 to_sql = f" TO {to_sql}" if to_sql else "" 2256 step_sql = self.sql(expression, "step") 2257 step_sql = f" STEP {step_sql}" if step_sql else "" 2258 interpolated_values = [ 2259 f"{self.sql(named_expression, 'alias')} AS {self.sql(named_expression, 'this')}" 2260 for named_expression in expression.args.get("interpolate") or [] 2261 ] 2262 interpolate = ( 2263 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2264 ) 2265 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2276 def ordered_sql(self, expression: exp.Ordered) -> str: 2277 desc = expression.args.get("desc") 2278 asc = not desc 2279 2280 nulls_first = expression.args.get("nulls_first") 2281 nulls_last = not nulls_first 2282 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2283 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2284 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2285 2286 this = self.sql(expression, "this") 2287 2288 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2289 nulls_sort_change = "" 2290 if nulls_first and ( 2291 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2292 ): 2293 nulls_sort_change = " NULLS FIRST" 2294 elif ( 2295 nulls_last 2296 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2297 and not nulls_are_last 2298 ): 2299 nulls_sort_change = " NULLS LAST" 2300 2301 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2302 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2303 window = expression.find_ancestor(exp.Window, exp.Select) 2304 if isinstance(window, exp.Window) and window.args.get("spec"): 2305 self.unsupported( 2306 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2307 ) 2308 nulls_sort_change = "" 2309 elif self.NULL_ORDERING_SUPPORTED is None: 2310 if expression.this.is_int: 2311 self.unsupported( 2312 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2313 ) 2314 elif not isinstance(expression.this, exp.Rand): 2315 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2316 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2317 nulls_sort_change = "" 2318 2319 with_fill = self.sql(expression, "with_fill") 2320 with_fill = f" {with_fill}" if with_fill else "" 2321 2322 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2332 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2333 partition = self.partition_by_sql(expression) 2334 order = self.sql(expression, "order") 2335 measures = self.expressions(expression, key="measures") 2336 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2337 rows = self.sql(expression, "rows") 2338 rows = self.seg(rows) if rows else "" 2339 after = self.sql(expression, "after") 2340 after = self.seg(after) if after else "" 2341 pattern = self.sql(expression, "pattern") 2342 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2343 definition_sqls = [ 2344 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2345 for definition in expression.args.get("define", []) 2346 ] 2347 definitions = self.expressions(sqls=definition_sqls) 2348 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2349 body = "".join( 2350 ( 2351 partition, 2352 order, 2353 measures, 2354 rows, 2355 after, 2356 pattern, 2357 define, 2358 ) 2359 ) 2360 alias = self.sql(expression, "alias") 2361 alias = f" {alias}" if alias else "" 2362 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2364 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2365 limit = expression.args.get("limit") 2366 2367 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2368 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2369 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2370 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2371 2372 return csv( 2373 *sqls, 2374 *[self.sql(join) for join in expression.args.get("joins") or []], 2375 self.sql(expression, "connect"), 2376 self.sql(expression, "match"), 2377 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2378 self.sql(expression, "prewhere"), 2379 self.sql(expression, "where"), 2380 self.sql(expression, "group"), 2381 self.sql(expression, "having"), 2382 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2383 self.sql(expression, "order"), 2384 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2385 *self.after_limit_modifiers(expression), 2386 self.options_modifier(expression), 2387 sep="", 2388 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2397 def offset_limit_modifiers( 2398 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2399 ) -> t.List[str]: 2400 return [ 2401 self.sql(expression, "offset") if fetch else self.sql(limit), 2402 self.sql(limit) if fetch else self.sql(expression, "offset"), 2403 ]
2410 def select_sql(self, expression: exp.Select) -> str: 2411 into = expression.args.get("into") 2412 if not self.SUPPORTS_SELECT_INTO and into: 2413 into.pop() 2414 2415 hint = self.sql(expression, "hint") 2416 distinct = self.sql(expression, "distinct") 2417 distinct = f" {distinct}" if distinct else "" 2418 kind = self.sql(expression, "kind") 2419 2420 limit = expression.args.get("limit") 2421 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2422 top = self.limit_sql(limit, top=True) 2423 limit.pop() 2424 else: 2425 top = "" 2426 2427 expressions = self.expressions(expression) 2428 2429 if kind: 2430 if kind in self.SELECT_KINDS: 2431 kind = f" AS {kind}" 2432 else: 2433 if kind == "STRUCT": 2434 expressions = self.expressions( 2435 sqls=[ 2436 self.sql( 2437 exp.Struct( 2438 expressions=[ 2439 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2440 if isinstance(e, exp.Alias) 2441 else e 2442 for e in expression.expressions 2443 ] 2444 ) 2445 ) 2446 ] 2447 ) 2448 kind = "" 2449 2450 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2451 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2452 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2453 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2454 sql = self.query_modifiers( 2455 expression, 2456 f"SELECT{top_distinct}{kind}{expressions}", 2457 self.sql(expression, "into", comment=False), 2458 self.sql(expression, "from", comment=False), 2459 ) 2460 2461 sql = self.prepend_ctes(expression, sql) 2462 2463 if not self.SUPPORTS_SELECT_INTO and into: 2464 if into.args.get("temporary"): 2465 table_kind = " TEMPORARY" 2466 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2467 table_kind = " UNLOGGED" 2468 else: 2469 table_kind = "" 2470 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2471 2472 return sql
2484 def star_sql(self, expression: exp.Star) -> str: 2485 except_ = self.expressions(expression, key="except", flat=True) 2486 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2487 replace = self.expressions(expression, key="replace", flat=True) 2488 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2489 rename = self.expressions(expression, key="rename", flat=True) 2490 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2491 return f"*{except_}{replace}{rename}"
2507 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2508 alias = self.sql(expression, "alias") 2509 alias = f"{sep}{alias}" if alias else "" 2510 sample = self.sql(expression, "sample") 2511 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2512 alias = f"{sample}{alias}" 2513 2514 # Set to None so it's not generated again by self.query_modifiers() 2515 expression.set("sample", None) 2516 2517 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2518 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2519 return self.prepend_ctes(expression, sql)
2525 def unnest_sql(self, expression: exp.Unnest) -> str: 2526 args = self.expressions(expression, flat=True) 2527 2528 alias = expression.args.get("alias") 2529 offset = expression.args.get("offset") 2530 2531 if self.UNNEST_WITH_ORDINALITY: 2532 if alias and isinstance(offset, exp.Expression): 2533 alias.append("columns", offset) 2534 2535 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2536 columns = alias.columns 2537 alias = self.sql(columns[0]) if columns else "" 2538 else: 2539 alias = self.sql(alias) 2540 2541 alias = f" AS {alias}" if alias else alias 2542 if self.UNNEST_WITH_ORDINALITY: 2543 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2544 else: 2545 if isinstance(offset, exp.Expression): 2546 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2547 elif offset: 2548 suffix = f"{alias} WITH OFFSET" 2549 else: 2550 suffix = alias 2551 2552 return f"UNNEST({args}){suffix}"
2561 def window_sql(self, expression: exp.Window) -> str: 2562 this = self.sql(expression, "this") 2563 partition = self.partition_by_sql(expression) 2564 order = expression.args.get("order") 2565 order = self.order_sql(order, flat=True) if order else "" 2566 spec = self.sql(expression, "spec") 2567 alias = self.sql(expression, "alias") 2568 over = self.sql(expression, "over") or "OVER" 2569 2570 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2571 2572 first = expression.args.get("first") 2573 if first is None: 2574 first = "" 2575 else: 2576 first = "FIRST" if first else "LAST" 2577 2578 if not partition and not order and not spec and alias: 2579 return f"{this} {alias}" 2580 2581 args = " ".join(arg for arg in (alias, first, partition, order, spec) if arg) 2582 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
2588 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2589 kind = self.sql(expression, "kind") 2590 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2591 end = ( 2592 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2593 or "CURRENT ROW" 2594 ) 2595 return f"{kind} BETWEEN {start} AND {end}"
def
bracket_offset_expressions( self, expression: sqlglot.expressions.Bracket) -> List[sqlglot.expressions.Expression]:
2623 def any_sql(self, expression: exp.Any) -> str: 2624 this = self.sql(expression, "this") 2625 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2626 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2627 this = self.wrap(this) 2628 return f"ANY{this}" 2629 return f"ANY {this}"
2634 def case_sql(self, expression: exp.Case) -> str: 2635 this = self.sql(expression, "this") 2636 statements = [f"CASE {this}" if this else "CASE"] 2637 2638 for e in expression.args["ifs"]: 2639 statements.append(f"WHEN {self.sql(e, 'this')}") 2640 statements.append(f"THEN {self.sql(e, 'true')}") 2641 2642 default = self.sql(expression, "default") 2643 2644 if default: 2645 statements.append(f"ELSE {default}") 2646 2647 statements.append("END") 2648 2649 if self.pretty and self.too_wide(statements): 2650 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2651 2652 return " ".join(statements)
2669 def trim_sql(self, expression: exp.Trim) -> str: 2670 trim_type = self.sql(expression, "position") 2671 2672 if trim_type == "LEADING": 2673 func_name = "LTRIM" 2674 elif trim_type == "TRAILING": 2675 func_name = "RTRIM" 2676 else: 2677 func_name = "TRIM" 2678 2679 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.Concat | sqlglot.expressions.ConcatWs) -> List[sqlglot.expressions.Expression]:
2681 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2682 args = expression.expressions 2683 if isinstance(expression, exp.ConcatWs): 2684 args = args[1:] # Skip the delimiter 2685 2686 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2687 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2688 2689 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2690 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2691 2692 return args
2694 def concat_sql(self, expression: exp.Concat) -> str: 2695 expressions = self.convert_concat_args(expression) 2696 2697 # Some dialects don't allow a single-argument CONCAT call 2698 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2699 return self.sql(expressions[0]) 2700 2701 return self.func("CONCAT", *expressions)
2712 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2713 expressions = self.expressions(expression, flat=True) 2714 reference = self.sql(expression, "reference") 2715 reference = f" {reference}" if reference else "" 2716 delete = self.sql(expression, "delete") 2717 delete = f" ON DELETE {delete}" if delete else "" 2718 update = self.sql(expression, "update") 2719 update = f" ON UPDATE {update}" if update else "" 2720 return f"FOREIGN KEY ({expressions}){reference}{delete}{update}"
2722 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2723 expressions = self.expressions(expression, flat=True) 2724 options = self.expressions(expression, key="options", flat=True, sep=" ") 2725 options = f" {options}" if options else "" 2726 return f"PRIMARY KEY ({expressions}){options}"
2746 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 2747 if isinstance(expression, exp.JSONPathPart): 2748 transform = self.TRANSFORMS.get(expression.__class__) 2749 if not callable(transform): 2750 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 2751 return "" 2752 2753 return transform(self, expression) 2754 2755 if isinstance(expression, int): 2756 return str(expression) 2757 2758 if self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 2759 escaped = expression.replace("'", "\\'") 2760 escaped = f"\\'{expression}\\'" 2761 else: 2762 escaped = expression.replace('"', '\\"') 2763 escaped = f'"{escaped}"' 2764 2765 return escaped
def
jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
2770 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 2771 null_handling = expression.args.get("null_handling") 2772 null_handling = f" {null_handling}" if null_handling else "" 2773 2774 unique_keys = expression.args.get("unique_keys") 2775 if unique_keys is not None: 2776 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 2777 else: 2778 unique_keys = "" 2779 2780 return_type = self.sql(expression, "return_type") 2781 return_type = f" RETURNING {return_type}" if return_type else "" 2782 encoding = self.sql(expression, "encoding") 2783 encoding = f" ENCODING {encoding}" if encoding else "" 2784 2785 return self.func( 2786 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 2787 *expression.expressions, 2788 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 2789 )
2794 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 2795 null_handling = expression.args.get("null_handling") 2796 null_handling = f" {null_handling}" if null_handling else "" 2797 return_type = self.sql(expression, "return_type") 2798 return_type = f" RETURNING {return_type}" if return_type else "" 2799 strict = " STRICT" if expression.args.get("strict") else "" 2800 return self.func( 2801 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 2802 )
2804 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 2805 this = self.sql(expression, "this") 2806 order = self.sql(expression, "order") 2807 null_handling = expression.args.get("null_handling") 2808 null_handling = f" {null_handling}" if null_handling else "" 2809 return_type = self.sql(expression, "return_type") 2810 return_type = f" RETURNING {return_type}" if return_type else "" 2811 strict = " STRICT" if expression.args.get("strict") else "" 2812 return self.func( 2813 "JSON_ARRAYAGG", 2814 this, 2815 suffix=f"{order}{null_handling}{return_type}{strict})", 2816 )
2818 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 2819 path = self.sql(expression, "path") 2820 path = f" PATH {path}" if path else "" 2821 nested_schema = self.sql(expression, "nested_schema") 2822 2823 if nested_schema: 2824 return f"NESTED{path} {nested_schema}" 2825 2826 this = self.sql(expression, "this") 2827 kind = self.sql(expression, "kind") 2828 kind = f" {kind}" if kind else "" 2829 return f"{this}{kind}{path}"
2834 def jsontable_sql(self, expression: exp.JSONTable) -> str: 2835 this = self.sql(expression, "this") 2836 path = self.sql(expression, "path") 2837 path = f", {path}" if path else "" 2838 error_handling = expression.args.get("error_handling") 2839 error_handling = f" {error_handling}" if error_handling else "" 2840 empty_handling = expression.args.get("empty_handling") 2841 empty_handling = f" {empty_handling}" if empty_handling else "" 2842 schema = self.sql(expression, "schema") 2843 return self.func( 2844 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 2845 )
2847 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 2848 this = self.sql(expression, "this") 2849 kind = self.sql(expression, "kind") 2850 path = self.sql(expression, "path") 2851 path = f" {path}" if path else "" 2852 as_json = " AS JSON" if expression.args.get("as_json") else "" 2853 return f"{this} {kind}{path}{as_json}"
2855 def openjson_sql(self, expression: exp.OpenJSON) -> str: 2856 this = self.sql(expression, "this") 2857 path = self.sql(expression, "path") 2858 path = f", {path}" if path else "" 2859 expressions = self.expressions(expression) 2860 with_ = ( 2861 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 2862 if expressions 2863 else "" 2864 ) 2865 return f"OPENJSON({this}{path}){with_}"
2867 def in_sql(self, expression: exp.In) -> str: 2868 query = expression.args.get("query") 2869 unnest = expression.args.get("unnest") 2870 field = expression.args.get("field") 2871 is_global = " GLOBAL" if expression.args.get("is_global") else "" 2872 2873 if query: 2874 in_sql = self.sql(query) 2875 elif unnest: 2876 in_sql = self.in_unnest_op(unnest) 2877 elif field: 2878 in_sql = self.sql(field) 2879 else: 2880 in_sql = f"({self.expressions(expression, flat=True)})" 2881 2882 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
2887 def interval_sql(self, expression: exp.Interval) -> str: 2888 unit = self.sql(expression, "unit") 2889 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 2890 unit = self.TIME_PART_SINGULARS.get(unit, unit) 2891 unit = f" {unit}" if unit else "" 2892 2893 if self.SINGLE_STRING_INTERVAL: 2894 this = expression.this.name if expression.this else "" 2895 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 2896 2897 this = self.sql(expression, "this") 2898 if this: 2899 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 2900 this = f" {this}" if unwrapped else f" ({this})" 2901 2902 return f"INTERVAL{this}{unit}"
2907 def reference_sql(self, expression: exp.Reference) -> str: 2908 this = self.sql(expression, "this") 2909 expressions = self.expressions(expression, flat=True) 2910 expressions = f"({expressions})" if expressions else "" 2911 options = self.expressions(expression, key="options", flat=True, sep=" ") 2912 options = f" {options}" if options else "" 2913 return f"REFERENCES {this}{expressions}{options}"
2915 def anonymous_sql(self, expression: exp.Anonymous) -> str: 2916 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 2917 parent = expression.parent 2918 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 2919 return self.func( 2920 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 2921 )
2941 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 2942 alias = expression.args["alias"] 2943 2944 identifier_alias = isinstance(alias, exp.Identifier) 2945 literal_alias = isinstance(alias, exp.Literal) 2946 2947 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 2948 alias.replace(exp.Literal.string(alias.output_name)) 2949 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 2950 alias.replace(exp.to_identifier(alias.output_name)) 2951 2952 return self.alias_sql(expression)
def
and_sql( self, expression: sqlglot.expressions.And, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
def
or_sql( self, expression: sqlglot.expressions.Or, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
def
xor_sql( self, expression: sqlglot.expressions.Xor, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
def
connector_sql( self, expression: sqlglot.expressions.Connector, op: str, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
2990 def connector_sql( 2991 self, 2992 expression: exp.Connector, 2993 op: str, 2994 stack: t.Optional[t.List[str | exp.Expression]] = None, 2995 ) -> str: 2996 if stack is not None: 2997 if expression.expressions: 2998 stack.append(self.expressions(expression, sep=f" {op} ")) 2999 else: 3000 stack.append(expression.right) 3001 if expression.comments and self.comments: 3002 for comment in expression.comments: 3003 if comment: 3004 op += f" /*{self.pad_comment(comment)}*/" 3005 stack.extend((op, expression.left)) 3006 return op 3007 3008 stack = [expression] 3009 sqls: t.List[str] = [] 3010 ops = set() 3011 3012 while stack: 3013 node = stack.pop() 3014 if isinstance(node, exp.Connector): 3015 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3016 else: 3017 sql = self.sql(node) 3018 if sqls and sqls[-1] in ops: 3019 sqls[-1] += f" {sql}" 3020 else: 3021 sqls.append(sql) 3022 3023 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3024 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3044 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3045 format_sql = self.sql(expression, "format") 3046 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3047 to_sql = self.sql(expression, "to") 3048 to_sql = f" {to_sql}" if to_sql else "" 3049 action = self.sql(expression, "action") 3050 action = f" {action}" if action else "" 3051 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{format_sql}{action})"
3065 def comment_sql(self, expression: exp.Comment) -> str: 3066 this = self.sql(expression, "this") 3067 kind = expression.args["kind"] 3068 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3069 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3070 expression_sql = self.sql(expression, "expression") 3071 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3073 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3074 this = self.sql(expression, "this") 3075 delete = " DELETE" if expression.args.get("delete") else "" 3076 recompress = self.sql(expression, "recompress") 3077 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3078 to_disk = self.sql(expression, "to_disk") 3079 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3080 to_volume = self.sql(expression, "to_volume") 3081 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3082 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3084 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3085 where = self.sql(expression, "where") 3086 group = self.sql(expression, "group") 3087 aggregates = self.expressions(expression, key="aggregates") 3088 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3089 3090 if not (where or group or aggregates) and len(expression.expressions) == 1: 3091 return f"TTL {self.expressions(expression, flat=True)}" 3092 3093 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3110 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3111 this = self.sql(expression, "this") 3112 3113 dtype = self.sql(expression, "dtype") 3114 if dtype: 3115 collate = self.sql(expression, "collate") 3116 collate = f" COLLATE {collate}" if collate else "" 3117 using = self.sql(expression, "using") 3118 using = f" USING {using}" if using else "" 3119 return f"ALTER COLUMN {this} SET DATA TYPE {dtype}{collate}{using}" 3120 3121 default = self.sql(expression, "default") 3122 if default: 3123 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3124 3125 comment = self.sql(expression, "comment") 3126 if comment: 3127 return f"ALTER COLUMN {this} COMMENT {comment}" 3128 3129 allow_null = expression.args.get("allow_null") 3130 drop = expression.args.get("drop") 3131 3132 if not drop and not allow_null: 3133 self.unsupported("Unsupported ALTER COLUMN syntax") 3134 3135 if allow_null is not None: 3136 keyword = "DROP" if drop else "SET" 3137 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3138 3139 return f"ALTER COLUMN {this} DROP DEFAULT"
3147 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3148 compound = " COMPOUND" if expression.args.get("compound") else "" 3149 this = self.sql(expression, "this") 3150 expressions = self.expressions(expression, flat=True) 3151 expressions = f"({expressions})" if expressions else "" 3152 return f"ALTER{compound} SORTKEY {this or expressions}"
3154 def renametable_sql(self, expression: exp.RenameTable) -> str: 3155 if not self.RENAME_TABLE_WITH_DB: 3156 # Remove db from tables 3157 expression = expression.transform( 3158 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3159 ).assert_is(exp.RenameTable) 3160 this = self.sql(expression, "this") 3161 return f"RENAME TO {this}"
3173 def alter_sql(self, expression: exp.Alter) -> str: 3174 actions = expression.args["actions"] 3175 3176 if isinstance(actions[0], exp.ColumnDef): 3177 actions = self.add_column_sql(expression) 3178 elif isinstance(actions[0], exp.Schema): 3179 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ") 3180 elif isinstance(actions[0], exp.Delete): 3181 actions = self.expressions(expression, key="actions", flat=True) 3182 elif isinstance(actions[0], exp.Query): 3183 actions = "AS " + self.expressions(expression, key="actions") 3184 else: 3185 actions = self.expressions(expression, key="actions", flat=True) 3186 3187 exists = " IF EXISTS" if expression.args.get("exists") else "" 3188 on_cluster = self.sql(expression, "cluster") 3189 on_cluster = f" {on_cluster}" if on_cluster else "" 3190 only = " ONLY" if expression.args.get("only") else "" 3191 options = self.expressions(expression, key="options") 3192 options = f", {options}" if options else "" 3193 kind = self.sql(expression, "kind") 3194 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster} {actions}{options}"
3196 def add_column_sql(self, expression: exp.Alter) -> str: 3197 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3198 return self.expressions( 3199 expression, 3200 key="actions", 3201 prefix="ADD COLUMN ", 3202 skip_first=True, 3203 ) 3204 return f"ADD {self.expressions(expression, key='actions', flat=True)}"
3214 def distinct_sql(self, expression: exp.Distinct) -> str: 3215 this = self.expressions(expression, flat=True) 3216 3217 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3218 case = exp.case() 3219 for arg in expression.expressions: 3220 case = case.when(arg.is_(exp.null()), exp.null()) 3221 this = self.sql(case.else_(f"({this})")) 3222 3223 this = f" {this}" if this else "" 3224 3225 on = self.sql(expression, "on") 3226 on = f" ON {on}" if on else "" 3227 return f"DISTINCT{this}{on}"
3256 def div_sql(self, expression: exp.Div) -> str: 3257 l, r = expression.left, expression.right 3258 3259 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3260 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3261 3262 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3263 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3264 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3265 3266 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3267 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3268 return self.sql( 3269 exp.cast( 3270 l / r, 3271 to=exp.DataType.Type.BIGINT, 3272 ) 3273 ) 3274 3275 return self.binary(expression, "/")
3363 def log_sql(self, expression: exp.Log) -> str: 3364 this = expression.this 3365 expr = expression.expression 3366 3367 if self.dialect.LOG_BASE_FIRST is False: 3368 this, expr = expr, this 3369 elif self.dialect.LOG_BASE_FIRST is None and expr: 3370 if this.name in ("2", "10"): 3371 return self.func(f"LOG{this.name}", expr) 3372 3373 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3374 3375 return self.func("LOG", this, expr)
3384 def binary(self, expression: exp.Binary, op: str) -> str: 3385 sqls: t.List[str] = [] 3386 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3387 binary_type = type(expression) 3388 3389 while stack: 3390 node = stack.pop() 3391 3392 if type(node) is binary_type: 3393 op_func = node.args.get("operator") 3394 if op_func: 3395 op = f"OPERATOR({self.sql(op_func)})" 3396 3397 stack.append(node.right) 3398 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3399 stack.append(node.left) 3400 else: 3401 sqls.append(self.sql(node)) 3402 3403 return "".join(sqls)
3405 def function_fallback_sql(self, expression: exp.Func) -> str: 3406 args = [] 3407 3408 for key in expression.arg_types: 3409 arg_value = expression.args.get(key) 3410 3411 if isinstance(arg_value, list): 3412 for value in arg_value: 3413 args.append(value) 3414 elif arg_value is not None: 3415 args.append(arg_value) 3416 3417 if self.normalize_functions: 3418 name = expression.sql_name() 3419 else: 3420 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3421 3422 return self.func(name, *args)
def
func( self, name: str, *args: Union[str, sqlglot.expressions.Expression, NoneType], prefix: str = '(', suffix: str = ')', normalize: bool = True) -> str:
3424 def func( 3425 self, 3426 name: str, 3427 *args: t.Optional[exp.Expression | str], 3428 prefix: str = "(", 3429 suffix: str = ")", 3430 normalize: bool = True, 3431 ) -> str: 3432 name = self.normalize_func(name) if normalize else name 3433 return f"{name}{prefix}{self.format_args(*args)}{suffix}"
3435 def format_args(self, *args: t.Optional[str | exp.Expression]) -> str: 3436 arg_sqls = tuple( 3437 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3438 ) 3439 if self.pretty and self.too_wide(arg_sqls): 3440 return self.indent("\n" + ",\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True) 3441 return ", ".join(arg_sqls)
def
format_time( self, expression: sqlglot.expressions.Expression, inverse_time_mapping: Optional[Dict[str, str]] = None, inverse_time_trie: Optional[Dict] = None) -> Optional[str]:
3446 def format_time( 3447 self, 3448 expression: exp.Expression, 3449 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3450 inverse_time_trie: t.Optional[t.Dict] = None, 3451 ) -> t.Optional[str]: 3452 return format_time( 3453 self.sql(expression, "format"), 3454 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3455 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3456 )
def
expressions( self, expression: Optional[sqlglot.expressions.Expression] = None, key: Optional[str] = None, sqls: Optional[Collection[Union[str, sqlglot.expressions.Expression]]] = None, flat: bool = False, indent: bool = True, skip_first: bool = False, skip_last: bool = False, sep: str = ', ', prefix: str = '', dynamic: bool = False, new_line: bool = False) -> str:
3458 def expressions( 3459 self, 3460 expression: t.Optional[exp.Expression] = None, 3461 key: t.Optional[str] = None, 3462 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3463 flat: bool = False, 3464 indent: bool = True, 3465 skip_first: bool = False, 3466 skip_last: bool = False, 3467 sep: str = ", ", 3468 prefix: str = "", 3469 dynamic: bool = False, 3470 new_line: bool = False, 3471 ) -> str: 3472 expressions = expression.args.get(key or "expressions") if expression else sqls 3473 3474 if not expressions: 3475 return "" 3476 3477 if flat: 3478 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3479 3480 num_sqls = len(expressions) 3481 result_sqls = [] 3482 3483 for i, e in enumerate(expressions): 3484 sql = self.sql(e, comment=False) 3485 if not sql: 3486 continue 3487 3488 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3489 3490 if self.pretty: 3491 if self.leading_comma: 3492 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3493 else: 3494 result_sqls.append( 3495 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3496 ) 3497 else: 3498 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3499 3500 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3501 if new_line: 3502 result_sqls.insert(0, "") 3503 result_sqls.append("") 3504 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3505 else: 3506 result_sql = "".join(result_sqls) 3507 3508 return ( 3509 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3510 if indent 3511 else result_sql 3512 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
3514 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3515 flat = flat or isinstance(expression.parent, exp.Properties) 3516 expressions_sql = self.expressions(expression, flat=flat) 3517 if flat: 3518 return f"{op} {expressions_sql}" 3519 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3521 def naked_property(self, expression: exp.Property) -> str: 3522 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3523 if not property_name: 3524 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3525 return f"{property_name} {self.sql(expression, 'this')}"
3533 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3534 this = self.sql(expression, "this") 3535 expressions = self.no_identify(self.expressions, expression) 3536 expressions = ( 3537 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3538 ) 3539 return f"{this}{expressions}"
3549 def when_sql(self, expression: exp.When) -> str: 3550 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3551 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3552 condition = self.sql(expression, "condition") 3553 condition = f" AND {condition}" if condition else "" 3554 3555 then_expression = expression.args.get("then") 3556 if isinstance(then_expression, exp.Insert): 3557 this = self.sql(then_expression, "this") 3558 this = f"INSERT {this}" if this else "INSERT" 3559 then = self.sql(then_expression, "expression") 3560 then = f"{this} VALUES {then}" if then else this 3561 elif isinstance(then_expression, exp.Update): 3562 if isinstance(then_expression.args.get("expressions"), exp.Star): 3563 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3564 else: 3565 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}" 3566 else: 3567 then = self.sql(then_expression) 3568 return f"WHEN {matched}{source}{condition} THEN {then}"
3570 def merge_sql(self, expression: exp.Merge) -> str: 3571 table = expression.this 3572 table_alias = "" 3573 3574 hints = table.args.get("hints") 3575 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3576 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3577 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3578 3579 this = self.sql(table) 3580 using = f"USING {self.sql(expression, 'using')}" 3581 on = f"ON {self.sql(expression, 'on')}" 3582 expressions = self.expressions(expression, sep=" ", indent=False) 3583 sep = self.sep() 3584 3585 return self.prepend_ctes( 3586 expression, f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{expressions}" 3587 )
3595 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3596 if not self.SUPPORTS_TO_NUMBER: 3597 self.unsupported("Unsupported TO_NUMBER function") 3598 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3599 3600 fmt = expression.args.get("format") 3601 if not fmt: 3602 self.unsupported("Conversion format is required for TO_NUMBER") 3603 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3604 3605 return self.func("TO_NUMBER", expression.this, fmt)
3607 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3608 this = self.sql(expression, "this") 3609 kind = self.sql(expression, "kind") 3610 settings_sql = self.expressions(expression, key="settings", sep=" ") 3611 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3612 return f"{this}({kind}{args})"
3627 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3628 expressions = self.expressions(expression, flat=True) 3629 expressions = f" {self.wrap(expressions)}" if expressions else "" 3630 buckets = self.sql(expression, "buckets") 3631 kind = self.sql(expression, "kind") 3632 buckets = f" BUCKETS {buckets}" if buckets else "" 3633 order = self.sql(expression, "order") 3634 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
3639 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 3640 expressions = self.expressions(expression, key="expressions", flat=True) 3641 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 3642 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 3643 buckets = self.sql(expression, "buckets") 3644 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
3646 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 3647 this = self.sql(expression, "this") 3648 having = self.sql(expression, "having") 3649 3650 if having: 3651 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 3652 3653 return self.func("ANY_VALUE", this)
3655 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 3656 transform = self.func("TRANSFORM", *expression.expressions) 3657 row_format_before = self.sql(expression, "row_format_before") 3658 row_format_before = f" {row_format_before}" if row_format_before else "" 3659 record_writer = self.sql(expression, "record_writer") 3660 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 3661 using = f" USING {self.sql(expression, 'command_script')}" 3662 schema = self.sql(expression, "schema") 3663 schema = f" AS {schema}" if schema else "" 3664 row_format_after = self.sql(expression, "row_format_after") 3665 row_format_after = f" {row_format_after}" if row_format_after else "" 3666 record_reader = self.sql(expression, "record_reader") 3667 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 3668 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
3670 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 3671 key_block_size = self.sql(expression, "key_block_size") 3672 if key_block_size: 3673 return f"KEY_BLOCK_SIZE = {key_block_size}" 3674 3675 using = self.sql(expression, "using") 3676 if using: 3677 return f"USING {using}" 3678 3679 parser = self.sql(expression, "parser") 3680 if parser: 3681 return f"WITH PARSER {parser}" 3682 3683 comment = self.sql(expression, "comment") 3684 if comment: 3685 return f"COMMENT {comment}" 3686 3687 visible = expression.args.get("visible") 3688 if visible is not None: 3689 return "VISIBLE" if visible else "INVISIBLE" 3690 3691 engine_attr = self.sql(expression, "engine_attr") 3692 if engine_attr: 3693 return f"ENGINE_ATTRIBUTE = {engine_attr}" 3694 3695 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 3696 if secondary_engine_attr: 3697 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 3698 3699 self.unsupported("Unsupported index constraint option.") 3700 return ""
3706 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 3707 kind = self.sql(expression, "kind") 3708 kind = f"{kind} INDEX" if kind else "INDEX" 3709 this = self.sql(expression, "this") 3710 this = f" {this}" if this else "" 3711 index_type = self.sql(expression, "index_type") 3712 index_type = f" USING {index_type}" if index_type else "" 3713 expressions = self.expressions(expression, flat=True) 3714 expressions = f" ({expressions})" if expressions else "" 3715 options = self.expressions(expression, key="options", sep=" ") 3716 options = f" {options}" if options else "" 3717 return f"{kind}{this}{index_type}{expressions}{options}"
3719 def nvl2_sql(self, expression: exp.Nvl2) -> str: 3720 if self.NVL2_SUPPORTED: 3721 return self.function_fallback_sql(expression) 3722 3723 case = exp.Case().when( 3724 expression.this.is_(exp.null()).not_(copy=False), 3725 expression.args["true"], 3726 copy=False, 3727 ) 3728 else_cond = expression.args.get("false") 3729 if else_cond: 3730 case.else_(else_cond, copy=False) 3731 3732 return self.sql(case)
3734 def comprehension_sql(self, expression: exp.Comprehension) -> str: 3735 this = self.sql(expression, "this") 3736 expr = self.sql(expression, "expression") 3737 iterator = self.sql(expression, "iterator") 3738 condition = self.sql(expression, "condition") 3739 condition = f" IF {condition}" if condition else "" 3740 return f"{this} FOR {expr} IN {iterator}{condition}"
3748 def predict_sql(self, expression: exp.Predict) -> str: 3749 model = self.sql(expression, "this") 3750 model = f"MODEL {model}" 3751 table = self.sql(expression, "expression") 3752 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 3753 parameters = self.sql(expression, "params_struct") 3754 return self.func("PREDICT", model, table, parameters or None)
3766 def toarray_sql(self, expression: exp.ToArray) -> str: 3767 arg = expression.this 3768 if not arg.type: 3769 from sqlglot.optimizer.annotate_types import annotate_types 3770 3771 arg = annotate_types(arg) 3772 3773 if arg.is_type(exp.DataType.Type.ARRAY): 3774 return self.sql(arg) 3775 3776 cond_for_null = arg.is_(exp.null()) 3777 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
3793 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 3794 this = expression.this 3795 time_format = self.format_time(expression) 3796 3797 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 3798 return self.sql( 3799 exp.cast( 3800 exp.StrToTime(this=this, format=expression.args["format"]), 3801 exp.DataType.Type.DATE, 3802 ) 3803 ) 3804 3805 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 3806 return self.sql(this) 3807 3808 return self.sql(exp.cast(this, exp.DataType.Type.DATE))
3820 def lastday_sql(self, expression: exp.LastDay) -> str: 3821 if self.LAST_DAY_SUPPORTS_DATE_PART: 3822 return self.function_fallback_sql(expression) 3823 3824 unit = expression.text("unit") 3825 if unit and unit != "MONTH": 3826 self.unsupported("Date parts are not supported in LAST_DAY.") 3827 3828 return self.func("LAST_DAY", expression.this)
3837 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 3838 if self.CAN_IMPLEMENT_ARRAY_ANY: 3839 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 3840 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 3841 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 3842 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 3843 3844 from sqlglot.dialects import Dialect 3845 3846 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 3847 if self.dialect.__class__ != Dialect: 3848 self.unsupported("ARRAY_ANY is unsupported") 3849 3850 return self.function_fallback_sql(expression)
3852 def struct_sql(self, expression: exp.Struct) -> str: 3853 expression.set( 3854 "expressions", 3855 [ 3856 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 3857 if isinstance(e, exp.PropertyEQ) 3858 else e 3859 for e in expression.expressions 3860 ], 3861 ) 3862 3863 return self.function_fallback_sql(expression)
3871 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 3872 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 3873 tables = f" {self.expressions(expression)}" 3874 3875 exists = " IF EXISTS" if expression.args.get("exists") else "" 3876 3877 on_cluster = self.sql(expression, "cluster") 3878 on_cluster = f" {on_cluster}" if on_cluster else "" 3879 3880 identity = self.sql(expression, "identity") 3881 identity = f" {identity} IDENTITY" if identity else "" 3882 3883 option = self.sql(expression, "option") 3884 option = f" {option}" if option else "" 3885 3886 partition = self.sql(expression, "partition") 3887 partition = f" {partition}" if partition else "" 3888 3889 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
3893 def convert_sql(self, expression: exp.Convert) -> str: 3894 to = expression.this 3895 value = expression.expression 3896 style = expression.args.get("style") 3897 safe = expression.args.get("safe") 3898 strict = expression.args.get("strict") 3899 3900 if not to or not value: 3901 return "" 3902 3903 # Retrieve length of datatype and override to default if not specified 3904 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 3905 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 3906 3907 transformed: t.Optional[exp.Expression] = None 3908 cast = exp.Cast if strict else exp.TryCast 3909 3910 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 3911 if isinstance(style, exp.Literal) and style.is_int: 3912 from sqlglot.dialects.tsql import TSQL 3913 3914 style_value = style.name 3915 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 3916 if not converted_style: 3917 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 3918 3919 fmt = exp.Literal.string(converted_style) 3920 3921 if to.this == exp.DataType.Type.DATE: 3922 transformed = exp.StrToDate(this=value, format=fmt) 3923 elif to.this == exp.DataType.Type.DATETIME: 3924 transformed = exp.StrToTime(this=value, format=fmt) 3925 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 3926 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 3927 elif to.this == exp.DataType.Type.TEXT: 3928 transformed = exp.TimeToStr(this=value, format=fmt) 3929 3930 if not transformed: 3931 transformed = cast(this=value, to=to, safe=safe) 3932 3933 return self.sql(transformed)
3989 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 3990 option = self.sql(expression, "this") 3991 3992 if expression.expressions: 3993 upper = option.upper() 3994 3995 # Snowflake FILE_FORMAT options are separated by whitespace 3996 sep = " " if upper == "FILE_FORMAT" else ", " 3997 3998 # Databricks copy/format options do not set their list of values with EQ 3999 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4000 values = self.expressions(expression, flat=True, sep=sep) 4001 return f"{option}{op}({values})" 4002 4003 value = self.sql(expression, "expression") 4004 4005 if not value: 4006 return option 4007 4008 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4009 4010 return f"{option}{op}{value}"
4012 def credentials_sql(self, expression: exp.Credentials) -> str: 4013 cred_expr = expression.args.get("credentials") 4014 if isinstance(cred_expr, exp.Literal): 4015 # Redshift case: CREDENTIALS <string> 4016 credentials = self.sql(expression, "credentials") 4017 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4018 else: 4019 # Snowflake case: CREDENTIALS = (...) 4020 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4021 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4022 4023 storage = self.sql(expression, "storage") 4024 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4025 4026 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4027 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4028 4029 iam_role = self.sql(expression, "iam_role") 4030 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4031 4032 region = self.sql(expression, "region") 4033 region = f" REGION {region}" if region else "" 4034 4035 return f"{credentials}{storage}{encryption}{iam_role}{region}"
4037 def copy_sql(self, expression: exp.Copy) -> str: 4038 this = self.sql(expression, "this") 4039 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4040 4041 credentials = self.sql(expression, "credentials") 4042 credentials = self.seg(credentials) if credentials else "" 4043 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4044 files = self.expressions(expression, key="files", flat=True) 4045 4046 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4047 params = self.expressions( 4048 expression, 4049 key="params", 4050 sep=sep, 4051 new_line=True, 4052 skip_last=True, 4053 skip_first=True, 4054 indent=self.COPY_PARAMS_ARE_WRAPPED, 4055 ) 4056 4057 if params: 4058 if self.COPY_PARAMS_ARE_WRAPPED: 4059 params = f" WITH ({params})" 4060 elif not self.pretty: 4061 params = f" {params}" 4062 4063 return f"COPY{this}{kind} {files}{credentials}{params}"
4068 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4069 on_sql = "ON" if expression.args.get("on") else "OFF" 4070 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4071 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4072 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4073 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4074 4075 if filter_col or retention_period: 4076 on_sql = self.func("ON", filter_col, retention_period) 4077 4078 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4080 def maskingpolicycolumnconstraint_sql( 4081 self, expression: exp.MaskingPolicyColumnConstraint 4082 ) -> str: 4083 this = self.sql(expression, "this") 4084 expressions = self.expressions(expression, flat=True) 4085 expressions = f" USING ({expressions})" if expressions else "" 4086 return f"MASKING POLICY {this}{expressions}"
4096 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4097 this = self.sql(expression, "this") 4098 expr = expression.expression 4099 4100 if isinstance(expr, exp.Func): 4101 # T-SQL's CLR functions are case sensitive 4102 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4103 else: 4104 expr = self.sql(expression, "expression") 4105 4106 return self.scope_resolution(expr, this)
4114 def rand_sql(self, expression: exp.Rand) -> str: 4115 lower = self.sql(expression, "lower") 4116 upper = self.sql(expression, "upper") 4117 4118 if lower and upper: 4119 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4120 return self.func("RAND", expression.this)
4122 def changes_sql(self, expression: exp.Changes) -> str: 4123 information = self.sql(expression, "information") 4124 information = f"INFORMATION => {information}" 4125 at_before = self.sql(expression, "at_before") 4126 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4127 end = self.sql(expression, "end") 4128 end = f"{self.seg('')}{end}" if end else "" 4129 4130 return f"CHANGES ({information}){at_before}{end}"
4132 def pad_sql(self, expression: exp.Pad) -> str: 4133 prefix = "L" if expression.args.get("is_left") else "R" 4134 4135 fill_pattern = self.sql(expression, "fill_pattern") or None 4136 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4137 fill_pattern = "' '" 4138 4139 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4145 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4146 generate_series = exp.GenerateSeries(**expression.args) 4147 4148 parent = expression.parent 4149 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4150 parent = parent.parent 4151 4152 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4153 return self.sql(exp.Unnest(expressions=[generate_series])) 4154 4155 if isinstance(parent, exp.Select): 4156 self.unsupported("GenerateSeries projection unnesting is not supported.") 4157 4158 return self.sql(generate_series)
def
arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4160 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4161 exprs = expression.expressions 4162 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4163 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4164 else: 4165 rhs = self.expressions(expression) 4166 4167 return self.func(name, expression.this, rhs)
4169 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4170 if self.SUPPORTS_CONVERT_TIMEZONE: 4171 return self.function_fallback_sql(expression) 4172 4173 source_tz = expression.args.get("source_tz") 4174 target_tz = expression.args.get("target_tz") 4175 timestamp = expression.args.get("timestamp") 4176 4177 if source_tz and timestamp: 4178 timestamp = exp.AtTimeZone( 4179 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4180 ) 4181 4182 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4183 4184 return self.sql(expr)
4186 def json_sql(self, expression: exp.JSON) -> str: 4187 this = self.sql(expression, "this") 4188 this = f" {this}" if this else "" 4189 4190 _with = expression.args.get("with") 4191 4192 if _with is None: 4193 with_sql = "" 4194 elif not _with: 4195 with_sql = " WITHOUT" 4196 else: 4197 with_sql = " WITH" 4198 4199 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4200 4201 return f"JSON{this}{with_sql}{unique_sql}"
4203 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4204 def _generate_on_options(arg: t.Any) -> str: 4205 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4206 4207 path = self.sql(expression, "path") 4208 returning = self.sql(expression, "returning") 4209 returning = f" RETURNING {returning}" if returning else "" 4210 4211 on_condition = self.sql(expression, "on_condition") 4212 on_condition = f" {on_condition}" if on_condition else "" 4213 4214 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4216 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4217 else_ = "ELSE " if expression.args.get("else_") else "" 4218 condition = self.sql(expression, "expression") 4219 condition = f"WHEN {condition} THEN " if condition else else_ 4220 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4221 return f"{condition}{insert}"
4229 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4230 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4231 empty = expression.args.get("empty") 4232 empty = ( 4233 f"DEFAULT {empty} ON EMPTY" 4234 if isinstance(empty, exp.Expression) 4235 else self.sql(expression, "empty") 4236 ) 4237 4238 error = expression.args.get("error") 4239 error = ( 4240 f"DEFAULT {error} ON ERROR" 4241 if isinstance(error, exp.Expression) 4242 else self.sql(expression, "error") 4243 ) 4244 4245 if error and empty: 4246 error = ( 4247 f"{empty} {error}" 4248 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4249 else f"{error} {empty}" 4250 ) 4251 empty = "" 4252 4253 null = self.sql(expression, "null") 4254 4255 return f"{empty}{error}{null}"
4257 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4258 this = self.sql(expression, "this") 4259 path = self.sql(expression, "path") 4260 4261 passing = self.expressions(expression, "passing") 4262 passing = f" PASSING {passing}" if passing else "" 4263 4264 on_condition = self.sql(expression, "on_condition") 4265 on_condition = f" {on_condition}" if on_condition else "" 4266 4267 path = f"{path}{passing}{on_condition}" 4268 4269 return self.func("JSON_EXISTS", this, path)
4271 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4272 array_agg = self.function_fallback_sql(expression) 4273 4274 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4275 parent = expression.parent 4276 if isinstance(parent, exp.Filter): 4277 parent_cond = parent.expression.this 4278 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4279 else: 4280 array_agg = f"{array_agg} FILTER(WHERE {self.sql(expression, 'this')} IS NOT NULL)" 4281 4282 return array_agg